/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2013-2019)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

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

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

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

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

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

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

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

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

#include "nodes.h"

#include <string.h>
#include <epoxy/gl.h>

#include <visu_nodes.h>
#include <visu_dataatomic.h>
#include <visu_dataspin.h>
#include <coreTools/toolColor.h>
#include <renderingMethods/elementAtomic.h>
#include <renderingMethods/elementSpin.h>
#include <renderingMethods/spinMethod.h>
#include <extraFunctions/plane.h>

#undef near
#undef far

/**
 * SECTION:nodes
 * @short_description: Defines methods to draw a set of nodes.
 *
 * <para></para>
 */

typedef struct _GlIds
{
  VisuElementRenderer *renderer;
  gulong notify_sig, size_sig, colorize_sig;
} GlIds;

enum
  {
    PROP_0,
    DATA_PROP,
    TYPE_PROP,
    MAX_SIZE_PROP,
    COLORIZER_PROP,
    N_PROP
  };
/* static GParamSpec *_properties[N_PROP]; */

enum
  {
   SHADER_OBJECT,
   SHADER_POINT,
   SHADER_PICK,
   N_SHADERS
  };

struct _VisuGlExtNodesPrivate
{
  gboolean dispose_has_run;

  VisuElementRendererEffects effect;

  guint nEle;
  GArray *glIds;

  /* GPU pointers for offscreen rendering for pick. */
  GLuint frameBuf, renderBuf, depthBuf;

  VisuGlView *view;
  VisuData *dataObj;
  gulong popId, posId, popIncId, popDecId, visId, unitId;
  gulong renId, detailsId;
  GList *colorizers;
  gulong dirtyId;
};

#define VISU_GL_EXT_NODES_ID "Nodes"

/* Local routines. */
static GObject* visu_gl_ext_nodes_constructor(GType gtype, guint nprops, GObjectConstructParam *props);
static void visu_gl_ext_nodes_dispose (GObject* obj);
static void visu_gl_ext_nodes_finalize(GObject* obj);
static void visu_gl_ext_nodes_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec);
static void visu_gl_ext_nodes_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec);
static void visu_gl_ext_nodes_rebuild(VisuGlExt *ext);
static void visu_gl_ext_nodes_release(VisuGlExt *ext);
static void visu_gl_ext_nodes_draw(VisuGlExt *ext);
static gboolean visu_gl_ext_nodes_setGlView(VisuGlExt *ext, VisuGlView *view);
static void visu_node_array_renderer_interface_init(VisuNodeArrayRendererInterface *iface);

static void _setPopulation(VisuGlExtNodes *nodes);

static void createAllRenderers(VisuGlExtNodes *ext);
static void _freeGlIds(GlIds *ids);
static VisuNodeArray* _getNodeArray(const VisuNodeArrayRenderer *self);
static gboolean _setNodeArray(VisuNodeArrayRenderer *self, VisuNodeArray *array);
static VisuElementRenderer* _getElementRenderer(const VisuNodeArrayRenderer *self,
                                                const VisuElement *element);
static gfloat _getMaxElementSize(VisuNodeArrayRenderer *node_array, guint *n);
static void _setColorizer(VisuNodeArrayRenderer *nodes,
                          VisuDataColorizer *colorizer);
static gboolean _pushColorizer(VisuNodeArrayRenderer *nodes,
                               VisuDataColorizer *colorizer);
static gboolean _removeColorizer(VisuNodeArrayRenderer *nodes,
                                 VisuDataColorizer *colorizer);
static VisuDataColorizer* _getColorizer(const VisuNodeArrayRenderer *nodes);

/* Signal callbacks. */
static void _setDirty(VisuGlExt *ext);
static void onPopulationInc(VisuData *dataObj, GArray *ids, gpointer data);
static void onPopulationDec(VisuData *dataObj, GArray *ids, gpointer data);
static void onPositionChanged(VisuData *dataObj, GArray *ids, gpointer data);
static void onVisibilityChanged(VisuData *dataObj, gpointer data);
static void onColorize(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElement *element);
static void onRenderer(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElementRenderer *renderer);
static void onSize(VisuGlExtNodes *ext, gfloat extent, VisuElementRenderer *renderer);
static void onUnits(VisuGlExtNodes *ext, gfloat fact);

G_DEFINE_TYPE_WITH_CODE(VisuGlExtNodes, visu_gl_ext_nodes, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtNodes)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_NODE_ARRAY_RENDERER,
                                              visu_node_array_renderer_interface_init))

static void visu_gl_ext_nodes_class_init(VisuGlExtNodesClass *klass)
{
  g_debug("Extension Nodes: creating the class of the object.");
  /* g_debug("                - adding new signals ;"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->constructor = visu_gl_ext_nodes_constructor;
  G_OBJECT_CLASS(klass)->dispose      = visu_gl_ext_nodes_dispose;
  G_OBJECT_CLASS(klass)->finalize     = visu_gl_ext_nodes_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_nodes_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_nodes_get_property;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_nodes_rebuild;
  VISU_GL_EXT_CLASS(klass)->release = visu_gl_ext_nodes_release;
  VISU_GL_EXT_CLASS(klass)->render = visu_gl_ext_renderBuffers;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_nodes_draw;
  VISU_GL_EXT_CLASS(klass)->setGlView = visu_gl_ext_nodes_setGlView;
  
  g_object_class_override_property(G_OBJECT_CLASS(klass), DATA_PROP, "data");
  g_object_class_override_property(G_OBJECT_CLASS(klass), TYPE_PROP, "type");
  g_object_class_override_property(G_OBJECT_CLASS(klass), MAX_SIZE_PROP, "max-element-size");
  g_object_class_override_property(G_OBJECT_CLASS(klass), COLORIZER_PROP, "colorizer");
}
static void visu_node_array_renderer_interface_init(VisuNodeArrayRendererInterface *iface)
{
  iface->getNodeArray = _getNodeArray;
  iface->setNodeArray = _setNodeArray;
  iface->getElement   = _getElementRenderer;
  iface->getExtent    = _getMaxElementSize;
  iface->getColorizer = _getColorizer;
  iface->pushColorizer = _pushColorizer;
  iface->removeColorizer = _removeColorizer;
}

static GObject* visu_gl_ext_nodes_constructor(GType gtype, guint nprops, GObjectConstructParam *props)
{
  guint i;

  for (i = 0; i < nprops; ++i)
    {
      if (!g_strcmp0(g_param_spec_get_name(props[i].pspec), "nGlObj"))
        g_value_set_uint(props[i].value, 1);
      else if (!g_strcmp0(g_param_spec_get_name(props[i].pspec), "nGlProg"))
        g_value_set_uint(props[i].value, N_SHADERS);
      else if (!g_strcmp0(g_param_spec_get_name(props[i].pspec), "withFog"))
        g_value_set_boolean(props[i].value, TRUE);
      else if (!g_strcmp0(g_param_spec_get_name(props[i].pspec), "priority"))
        g_value_set_uint(props[i].value, VISU_GL_EXT_PRIORITY_NODES);
    }

  return G_OBJECT_CLASS(visu_gl_ext_nodes_parent_class)->constructor(gtype, nprops, props);
}

static void visu_gl_ext_nodes_init(VisuGlExtNodes *obj)
{
  g_debug("Extension Nodes: initializing a new object (%p).",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_nodes_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->effect = VISU_ELEMENT_RENDERER_NO_EFFECT;
  obj->priv->glIds = g_array_new(FALSE, FALSE, sizeof(GlIds));
  g_array_set_clear_func(obj->priv->glIds, (GDestroyNotify)_freeGlIds);
  obj->priv->nEle = 0;
  obj->priv->view = (VisuGlView*)0;
  obj->priv->dataObj = (VisuData*)0;
  obj->priv->popId = 0;
  obj->priv->posId = 0;
  obj->priv->popIncId = 0;
  obj->priv->popDecId = 0;
  obj->priv->visId = 0;
  obj->priv->unitId = 0;
  obj->priv->colorizers = (GList*)0;
  obj->priv->frameBuf = 0;
  obj->priv->renderBuf = 0;
  obj->priv->depthBuf = 0;
}

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

  g_debug("Extension Nodes: dispose object %p.", (gpointer)obj);

  nodes = VISU_GL_EXT_NODES(obj);
  if (nodes->priv->dispose_has_run)
    return;
  nodes->priv->dispose_has_run = TRUE;

  visu_gl_ext_nodes_setGlView(VISU_GL_EXT(nodes), (VisuGlView*)0);
  _setNodeArray(VISU_NODE_ARRAY_RENDERER(nodes), (VisuNodeArray*)0);
  _setColorizer(VISU_NODE_ARRAY_RENDERER(nodes), (VisuDataColorizer*)0);
  g_list_free_full(VISU_GL_EXT_NODES(nodes)->priv->colorizers, g_object_unref);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_nodes_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_gl_ext_nodes_finalize(GObject* obj)
{
  VisuGlExtNodesPrivate *nodes;

  g_return_if_fail(obj);

  g_debug("Extension Nodes: finalize object %p.", (gpointer)obj);

  nodes = VISU_GL_EXT_NODES(obj)->priv;

  /* Free privs elements. */
  g_debug("Extension Nodes: free private nodes.");
  g_array_free(nodes->glIds, TRUE);

  /* Chain up to the parent class */
  g_debug("Extension Nodes: chain to parent.");
  G_OBJECT_CLASS(visu_gl_ext_nodes_parent_class)->finalize(obj);
  g_debug("Extension Nodes: freeing ... OK.");
}
static void visu_gl_ext_nodes_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec)
{
  VisuGlExtNodes *self = VISU_GL_EXT_NODES(obj);

  g_debug("Extension Nodes: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      g_value_set_object(value, self->priv->dataObj);
      g_debug("%p.", g_value_get_object(value));
      break;
    case TYPE_PROP:
      g_value_set_gtype(value, (self->priv->dataObj) ?
                        G_OBJECT_TYPE(self->priv->dataObj) : G_TYPE_NONE);
      g_debug("%d.", (gint)g_value_get_gtype(value));
      break;
    case MAX_SIZE_PROP:
      g_value_set_float(value, visu_node_array_renderer_getMaxElementSize(VISU_NODE_ARRAY_RENDERER(obj), (guint*)0));
      g_debug("%g.", g_value_get_float(value));
      break;
    case COLORIZER_PROP:
      g_value_set_object(value, self->priv->colorizers ? self->priv->colorizers->data : NULL);
      g_debug("%p.", g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_nodes_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec)
{
  g_debug("Extension Nodes: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      g_debug("%p", g_value_get_object(value));
      visu_node_array_renderer_setNodeArray(VISU_NODE_ARRAY_RENDERER(obj),
                                            VISU_NODE_ARRAY(g_value_get_object(value)));
      break;
    case COLORIZER_PROP:
      g_debug("%p.", g_value_get_object(value));
      _pushColorizer(VISU_NODE_ARRAY_RENDERER(obj), g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_nodes_new:
 *
 * Creates a new #VisuGlExt to draw a set of nodes. It can be used
 * also for picking, see visu_gl_ext_nodes_getSelection().
 *
 * Since: 3.7
 *
 * Returns: a pointer to the VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtNodes* visu_gl_ext_nodes_new()
{
  char *name = VISU_GL_EXT_NODES_ID;

  g_debug("Extension Nodes: new object.");
  
  return g_object_new(VISU_TYPE_GL_EXT_NODES, "name", name, "label", _(name),
                      "description", _("Draw all the nodes."), NULL);
}

static gboolean _setGlIdRenderer(GlIds *ids, VisuElementRenderer *renderer,
                                 VisuGlExtNodes *ext)
{
  g_debug("Extension Nodes: defining renderer for %p.", (gpointer)ids);
  if (ids->renderer == renderer)
    return FALSE;

  if (ids->renderer)
    {
      g_signal_handler_disconnect(visu_element_renderer_getElement(ids->renderer),
                                  ids->colorize_sig);
      g_signal_handler_disconnect(ids->renderer, ids->notify_sig);
      g_signal_handler_disconnect(ids->renderer, ids->size_sig);
      visu_element_renderer_setGlView(ids->renderer, (VisuGlView*)0);
      g_debug("Extension Nodes: free renderer %p.", (gpointer)ids->renderer);
      g_object_unref(ids->renderer);
    }
  ids->renderer = renderer;
  if (renderer)
    {
      g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), FALSE);
      g_debug("Extension Nodes: ref renderer %p.", (gpointer)renderer);
      g_object_ref(renderer);
      visu_element_renderer_setGlView(renderer, ext->priv->view);
      ids->notify_sig = g_signal_connect_swapped(renderer, "notify",
                                                 G_CALLBACK(onRenderer), ext);
      ids->size_sig = g_signal_connect_swapped(renderer, "size-changed",
                                               G_CALLBACK(onSize), ext);
      ids->colorize_sig = g_signal_connect_swapped(visu_element_renderer_getElement(renderer), "notify::colorizable",
                                                   G_CALLBACK(onColorize), ext);
    }
  return TRUE;
}
static void _freeGlIds(GlIds *ids)
{
  g_debug("Extension Nodes: free ids %p.", (gpointer)ids);
  _setGlIdRenderer(ids, (VisuElementRenderer*)0, (VisuGlExtNodes*)0);
}
static GlIds* _getGlIds(VisuGlExtNodes *ext, const VisuElement *element)
{
  GlIds *ids;
  guint i;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), (GlIds*)0);
  for (i = 0; i < ext->priv->nEle; i++)
    {
      ids = &g_array_index(ext->priv->glIds, GlIds, i);
      if (visu_element_renderer_getElement(ids->renderer) == element)
        return ids;
    }
  return (GlIds*)0;
}

static void createAllRenderers(VisuGlExtNodes *ext)
{
  VisuNodeArray *array;
  GlIds *ids;
  guint i;
  guint len;
  GArray *elements;
  VisuElementRenderer *renderer;

  g_return_if_fail(VISU_IS_GL_EXT_NODES(ext));

  array = VISU_NODE_ARRAY(ext->priv->dataObj);

  elements = (GArray*)0;
  if (array)
    g_object_get(G_OBJECT(array), "elements", &elements, NULL);
  ext->priv->nEle = elements ? elements->len : 0;
  g_debug("Extension Nodes (%p): setup population at %d.",
              (gpointer)ext, ext->priv->nEle);

  if (ext->priv->nEle > ext->priv->glIds->len)
    {
      g_debug(" | increase nodes gl ids (%d).", ext->priv->glIds->len);
      len = ext->priv->glIds->len;
      /* Change glIds size according to nEle. */
      ext->priv->glIds = g_array_set_size(ext->priv->glIds, ext->priv->nEle);
      for (; len < ext->priv->nEle; len++)
        {
          ids = &g_array_index(ext->priv->glIds, GlIds, len);
          ids->renderer = (VisuElementRenderer*)0;
        }
    }

  g_debug("Extension Nodes: create OpenGl elements for"
          " all VisuElement used in given VisuNodeArray %p.", (gpointer)array);
  i = 0;
  if (array)
    {
      g_return_if_fail(ext->priv->glIds->len >= elements->len);
      for (; i < elements->len; i++)
        {
          ids = &g_array_index(ext->priv->glIds, GlIds, i);
          
          renderer = (VisuElementRenderer*)0;
          if (VISU_IS_DATA_SPIN(ext->priv->dataObj) &&
              (!VISU_IS_ELEMENT_SPIN(ids->renderer) ||
               visu_element_renderer_getElement(ids->renderer) !=
               g_array_index(elements, VisuElement*, i)))
            {
              renderer = VISU_ELEMENT_RENDERER(visu_element_spin_new(g_array_index(elements, VisuElement*, i)));
              visu_element_spin_bindToPool(VISU_ELEMENT_SPIN(renderer));
            }
          else if (!VISU_IS_DATA_SPIN(ext->priv->dataObj) &&
                   (!VISU_IS_ELEMENT_ATOMIC(ids->renderer) ||
                    VISU_IS_ELEMENT_SPIN(ids->renderer) ||
                    visu_element_renderer_getElement(ids->renderer) !=
                    g_array_index(elements, VisuElement*, i)))
            {
              ToolUnits units = visu_box_getUnit(visu_boxed_getBox(VISU_BOXED(ext->priv->dataObj)));
              renderer = VISU_ELEMENT_RENDERER(visu_element_atomic_new(g_array_index(elements, VisuElement*, i)));
              visu_element_atomic_bindToPool(VISU_ELEMENT_ATOMIC(renderer));
              if (units != TOOL_UNITS_UNDEFINED)
                visu_element_atomic_setUnits(VISU_ELEMENT_ATOMIC(renderer), units);
            }

          if (renderer)
            {
              _setGlIdRenderer(ids, renderer, ext);
              g_object_unref(renderer);
            }
        }
    }
  if (elements)
    g_array_unref(elements);
  ext->priv->glIds = g_array_set_size(ext->priv->glIds, ext->priv->nEle);
  g_debug("Extension Nodes: creation done for all elements.");
}

static void _setPopulation(VisuGlExtNodes *nodes)
{
  createAllRenderers(nodes);
  g_signal_emit_by_name(nodes, "nodes::population", (GArray*)0);
  g_signal_emit_by_name(nodes, "nodes::population-set", (GArray*)0);
}
static gboolean _setNodeArray(VisuNodeArrayRenderer *self, VisuNodeArray *array)
{
  VisuData *dataObj;
  VisuGlExtNodes *nodes;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), FALSE);

  nodes = VISU_GL_EXT_NODES(self);
  dataObj = VISU_DATA(array);

  if (nodes->priv->dataObj == dataObj)
    return FALSE;

  if (nodes->priv->dataObj)
    {
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->unitId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->posId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popIncId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popDecId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->visId);
      if (nodes->priv->renId)
        g_signal_handler_disconnect(visu_method_spin_getDefault(), nodes->priv->renId);
      g_object_unref(nodes->priv->dataObj);
    }
  nodes->priv->dataObj = dataObj;
  nodes->priv->renId = 0;
  if (dataObj)
    {
      g_object_ref(dataObj);
      nodes->priv->unitId =
        g_signal_connect_swapped(G_OBJECT(dataObj), "unit-changed",
                                 G_CALLBACK(onUnits), (gpointer)nodes);
      nodes->priv->popId =
        g_signal_connect_swapped(G_OBJECT(dataObj), "notify::elements",
                                 G_CALLBACK(_setPopulation), (gpointer)nodes);
      nodes->priv->posId =
        g_signal_connect_after(G_OBJECT(dataObj), "position-changed",
                               G_CALLBACK(onPositionChanged), (gpointer)nodes);
      nodes->priv->popIncId =
        g_signal_connect_after(G_OBJECT(dataObj), "PopulationIncrease",
                               G_CALLBACK(onPopulationInc), (gpointer)nodes);
      nodes->priv->popDecId =
        g_signal_connect_after(G_OBJECT(dataObj), "PopulationDecrease",
                               G_CALLBACK(onPopulationDec), (gpointer)nodes);
      nodes->priv->visId =
        g_signal_connect_after(dataObj, "visibility-changed",
                               G_CALLBACK(onVisibilityChanged), (gpointer)nodes);
      if (VISU_IS_DATA_SPIN(dataObj))
        nodes->priv->renId = g_signal_connect_swapped
          (G_OBJECT(visu_method_spin_getDefault()), "notify",
           G_CALLBACK(_setDirty), nodes);
    }
  _setPopulation(nodes);
  if (_getColorizer(VISU_NODE_ARRAY_RENDERER(nodes)))
    visu_sourceable_follow(VISU_SOURCEABLE(_getColorizer(VISU_NODE_ARRAY_RENDERER(nodes))), dataObj);
  visu_gl_ext_setDirty(VISU_GL_EXT(nodes), VISU_GL_DRAW_REQUIRED);

  return TRUE;
}
/**
 * visu_gl_ext_nodes_renderAsBalls:
 * @nodes: a #VisuGlExtNodes object.
 * @factor: a positive factor.
 *
 * Setup all atomic rendering to use spheres. The radius of the
 * spheres are taken as the covalent radius, times @factor. All
 * elements are made visible and their color is set to the default
 * color for this element in the periodic table.
 *
 * Since: 3.9
 * 
 */
void visu_gl_ext_nodes_renderAsBalls(VisuGlExtNodes *nodes, gfloat factor)
{
  guint i;
  GlIds *ids;
  ToolUnits units;

  if (!VISU_IS_DATA_ATOMIC(nodes->priv->dataObj))
    return;

  units = visu_box_getUnit(visu_boxed_getBox(VISU_BOXED(nodes->priv->dataObj)));
  for (i = 0; i < nodes->priv->nEle; i++)
    {
      ids = &g_array_index(nodes->priv->glIds, GlIds, i);
      visu_element_setRendered(visu_element_renderer_getElement(ids->renderer), TRUE);
      visu_element_renderer_useDefaultColor(ids->renderer);
      visu_element_atomic_setShape(VISU_ELEMENT_ATOMIC(ids->renderer),
                                   VISU_ELEMENT_ATOMIC_SPHERE);
      visu_element_atomic_useCovalentRadius(VISU_ELEMENT_ATOMIC(ids->renderer),
                                            factor);
      if (units != TOOL_UNITS_UNDEFINED)
        visu_element_atomic_setUnits(VISU_ELEMENT_ATOMIC(ids->renderer), units);
    }
  visu_gl_ext_setActive(VISU_GL_EXT(nodes), TRUE);
}
/**
 * visu_gl_ext_nodes_setMaterialEffect:
 * @nodes: a #VisuGlExtNodes object.
 * @effect: a #VisuElementRendererEffects id.
 *
 * Changes the effect applied on the color used to render #VisuElement.
 *
 * Since: 3.7
 *
 * Returns: TRUE if the effect has been changed.
 **/
gboolean visu_gl_ext_nodes_setMaterialEffect(VisuGlExtNodes *nodes,
                                             VisuElementRendererEffects effect)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);

  if (nodes->priv->effect == effect)
    return FALSE;

  nodes->priv->effect = effect;
  visu_gl_ext_setDirty(VISU_GL_EXT(nodes), VISU_GL_DRAW_REQUIRED);
  return TRUE;
}
static VisuNodeArray* _getNodeArray(const VisuNodeArrayRenderer *self)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), (VisuNodeArray*)0);

  return VISU_NODE_ARRAY(VISU_GL_EXT_NODES(self)->priv->dataObj);
}
static VisuElementRenderer* _getElementRenderer(const VisuNodeArrayRenderer *self,
                                                const VisuElement *element)
{
  GlIds *ids;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), (VisuElementRenderer*)0);

  ids = _getGlIds(VISU_GL_EXT_NODES(self), element);
  if (!ids)
    return (VisuElementRenderer*)0;

  return ids->renderer;
}
static gfloat _getMaxElementSize(VisuNodeArrayRenderer *node_array, guint *n)
{
  guint i;
  GlIds *ids;
  gfloat s;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(node_array), 0.f);
  
  s = 0.f;
  for (i = 0; i < VISU_GL_EXT_NODES(node_array)->priv->nEle; i += 1)
    {
      ids = &g_array_index(VISU_GL_EXT_NODES(node_array)->priv->glIds, GlIds, i);
      if (ids->renderer)
        s = MAX(s, visu_element_renderer_getExtent(ids->renderer));
    }
  if (n)
    *n = i;
  return s;
}
static void visu_gl_ext_nodes_rebuild(VisuGlExt *ext)
{
  VisuGlExtNodes *self = VISU_GL_EXT_NODES(ext);
  const char *vertex =
    "#version 130\n"
    "in vec3 position;\n"
    "uniform mat4 mvp;\n"
    "\n"
    "void main() {\n"
    "  gl_Position = mvp * vec4(position, 1.0);\n"
     "}";
  const char *fragment =
    "#version 130\n"
    "out vec4 outputColor;\n"
    "uniform uvec3 color;\n"
    "\n"
    "void main() {\n"
    "  outputColor = vec4(vec3(color) / 255., 1.);\n"
    "}";
  GError *error = (GError*)0;

  if (!visu_gl_ext_setShaderById(ext, SHADER_OBJECT, VISU_GL_SHADER_MATERIAL, &error))
    {
      g_warning("Cannot create node shader: %s", error->message);
      g_clear_error(&error);
    }
  if (!visu_gl_ext_setShaderById(ext, SHADER_POINT, VISU_GL_SHADER_FLAT, &error))
    {
      g_warning("Cannot create node shader: %s", error->message);
      g_clear_error(&error);
    }
  if (!visu_gl_ext_setShader(ext, SHADER_PICK, vertex, fragment, (const char**)0, &error))
    {
      g_warning("Cannot create pick shader: %s", error->message);
      g_clear_error(&error);
    }
  if (!self->priv->frameBuf)
    glGenFramebuffers(1, &self->priv->frameBuf);
  if (!self->priv->frameBuf)
    g_warning("Cannot create framebuffer for pick.");
  if (!self->priv->renderBuf)
    glGenRenderbuffers(1, &self->priv->renderBuf);
  if (!self->priv->renderBuf)
    g_warning("Cannot create renderbuffer for pick.");
  if (!self->priv->depthBuf)
    glGenRenderbuffers(1, &self->priv->depthBuf);
  if (!self->priv->depthBuf)
    g_warning("Cannot create depthbuffer for pick.");
}
static void visu_gl_ext_nodes_release(VisuGlExt *ext)
{
  VisuGlExtNodes *self = VISU_GL_EXT_NODES(ext);

  if (self->priv->frameBuf)
    glDeleteFramebuffers(1, &self->priv->frameBuf);
  self->priv->frameBuf = 0;
  if (self->priv->renderBuf)
    glDeleteRenderbuffers(1, &self->priv->renderBuf);
  self->priv->renderBuf = 0;
  if (self->priv->depthBuf)
    glDeleteRenderbuffers(1, &self->priv->depthBuf);
  self->priv->depthBuf = 0;
}
struct _BufClosure
{
  VisuGlExt *ext;
  guint iBuf, iNode;
};
static void _commitBuf(const GArray *vertices, guint id, const gfloat rgba[4], const gfloat material[5], gpointer data)
{
  struct _BufClosure *closure = (struct _BufClosure*)data;
  visu_gl_ext_layoutBufferWithColor(closure->ext, closure->iBuf + id, vertices, rgba, material);
}
static void _drawNodes(VisuGlExtNodes *nodes, VisuCommitVertices commitFunc)
{
  guint i, j, nBuf, iBuf;
  GlIds *ids;
  struct _BufClosure closure;

  visu_gl_ext_clearBuffers(VISU_GL_EXT(nodes));

  /* Nothing to draw; */
  if(!nodes->priv->dataObj || !nodes->priv->view)
    return;

  closure.ext = VISU_GL_EXT(nodes);
  nBuf = 0;
  for (i = 0; i < nodes->priv->nEle; i += 1)
    {
      ids = &g_array_index(nodes->priv->glIds, GlIds, i);
      if (ids->renderer)
        nBuf += visu_element_renderer_getVerticeBufferDim(ids->renderer);
    }
  if (!nBuf)
    return;
  iBuf = 0;
  visu_gl_ext_bookBuffers(VISU_GL_EXT(nodes), nBuf);
  for (i = 0; i < nodes->priv->nEle; i += 1)
    {
      closure.iBuf = iBuf;
      ids = &g_array_index(nodes->priv->glIds, GlIds, i);
      if (ids->renderer)
        {
          VisuElement *element = visu_element_renderer_getElement(ids->renderer);
          if (visu_element_getRendered(element))
            {
              GArray *vertices[10];
              VisuNodeArrayIter iter;
              VisuGlExtPackingModels packing;
              VisuGlExtPrimitives primitive;
              g_debug("Extension Nodes: create list of element '%s'.", element->name);
              for (j = 0; j < visu_element_renderer_getVerticeBufferDim(ids->renderer); j++)
                {
                  visu_element_renderer_getVerticeLayout(ids->renderer, j, &packing, &primitive);
                  if (packing == VISU_GL_XYZ)
                    vertices[j] = visu_gl_ext_startBuffer(VISU_GL_EXT(nodes), SHADER_POINT, iBuf + j, packing, primitive);
                  else if (packing == VISU_GL_XYZ_NRM)
                    vertices[j] = visu_gl_ext_startBuffer(VISU_GL_EXT(nodes), SHADER_OBJECT, iBuf + j, packing, primitive);
                  else
                    g_return_if_reached();
                }
              visu_node_array_iter_new(VISU_NODE_ARRAY(nodes->priv->dataObj), &iter);
              iter.element = element;
              for(visu_node_array_iterRestartNode(VISU_NODE_ARRAY(nodes->priv->dataObj), &iter);
                  iter.node;
                  visu_node_array_iterNextNode(VISU_NODE_ARRAY(nodes->priv->dataObj), &iter))
                if (iter.node->rendered)
                  {
                    closure.iNode = iter.node->number + 1;
                    visu_element_renderer_addVertices(ids->renderer,
                                                      _getColorizer(VISU_NODE_ARRAY_RENDERER(nodes)),
                                                      nodes->priv->dataObj, iter.node,
                                                      vertices, commitFunc, &closure);
                  }
              for (j = 0; j < visu_element_renderer_getVerticeBufferDim(ids->renderer); j++)
                visu_gl_ext_takeBuffer(VISU_GL_EXT(nodes), iBuf + j, vertices[j]);
              iBuf += visu_element_renderer_getVerticeBufferDim(ids->renderer);
            }
        }
    }
}
static void visu_gl_ext_nodes_draw(VisuGlExt *ext)
{
  _drawNodes(VISU_GL_EXT_NODES(ext), _commitBuf);
}
static gboolean visu_gl_ext_nodes_setGlView(VisuGlExt *ext, VisuGlView *view)
{
  VisuGlExtNodesPrivate *priv = VISU_GL_EXT_NODES(ext)->priv;
  guint i;
  GlIds *ids;

  if (priv->view == view)
    return FALSE;

  if (priv->view)
    {
      g_signal_handler_disconnect(priv->view, priv->detailsId);
      g_object_unref(priv->view);
    }
  if (view)
    {
      g_object_ref(view);
      priv->detailsId = g_signal_connect_swapped(G_OBJECT(view), "DetailLevelChanged",
                                                 G_CALLBACK(_setDirty), ext);
    }
  priv->view = view;

  for (i = 0; i < priv->nEle; i += 1)
    {
      ids = &g_array_index(priv->glIds, GlIds, i);
      if (ids->renderer)
        visu_element_renderer_setGlView(ids->renderer, view);
    }

  visu_gl_ext_setDirty(ext, VISU_GL_DRAW_REQUIRED);
  return TRUE;
}

/********************/
/* Signal handlers. */
/********************/
static void _setDirty(VisuGlExt *ext)
{
  g_debug("Extension Nodes: set dirty.");
  visu_gl_ext_setDirty(ext, VISU_GL_DRAW_REQUIRED);
}
static void onPopulationInc(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  g_signal_emit_by_name(data, "nodes::population", ids);
  g_signal_emit_by_name(data, "nodes::population-increase", ids);
  visu_gl_ext_setDirty(VISU_GL_EXT(data), VISU_GL_DRAW_REQUIRED);
}
static void onPopulationDec(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  g_signal_emit_by_name(data, "nodes::population", ids);
  g_signal_emit_by_name(data, "nodes::population-decrease", ids);
  visu_gl_ext_setDirty(VISU_GL_EXT(data), VISU_GL_DRAW_REQUIRED);
}
static void onColorize(VisuGlExtNodes *ext, GParamSpec *pspec _U_, VisuElement *element _U_)
{
  if (!_getColorizer(VISU_NODE_ARRAY_RENDERER(ext)))
    return;
  
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), VISU_GL_DRAW_REQUIRED);
}
static void onRenderer(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElementRenderer *renderer)
{
  gchar *sig;

  g_debug("Extension Nodes: caught 'notify::%s' for ext %p.",
              g_param_spec_get_name(pspec), (gpointer)ext);
  sig = g_strdup_printf("%s::%s", "element-notify", g_param_spec_get_name(pspec));
  g_signal_emit_by_name(ext, sig, renderer);
  g_free(sig);
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), VISU_GL_DRAW_REQUIRED);
}
static void onSize(VisuGlExtNodes *ext, gfloat extent, VisuElementRenderer *renderer)
{
  g_debug("Extension Nodes: caught 'size-changed' for ext %p.",
              (gpointer)ext);
  g_signal_emit_by_name(ext, "element-size-changed", renderer, extent);
  g_object_notify(G_OBJECT(ext), "max-element-size");
}
static void onPositionChanged(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  g_debug("Extension Nodes: caught 'onPositionChanged'.");
  /* if (ele) */
  /*   createNodes(ext, _getGlIds(ext, ele)); */
  /* else */
  g_signal_emit_by_name(data, "nodes::position", ids);
  visu_gl_ext_setDirty(VISU_GL_EXT(data), VISU_GL_DRAW_REQUIRED);
}
static void onVisibilityChanged(VisuData *dataObj _U_, gpointer data)
{
  g_signal_emit_by_name(data, "nodes::visibility", (GArray*)0);
  visu_gl_ext_setDirty(VISU_GL_EXT(data), VISU_GL_DRAW_REQUIRED);
}
static void onUnits(VisuGlExtNodes *ext, gfloat fact _U_)
{
  guint i;
  GlIds *ids;
  ToolUnits units;

  if (!VISU_IS_DATA_ATOMIC(ext->priv->dataObj))
    return;

  g_message("Extension Nodes: caught 'unit-changed'.");
  units = visu_box_getUnit(visu_boxed_getBox(VISU_BOXED(ext->priv->dataObj)));
  if (units == TOOL_UNITS_UNDEFINED)
    return;
  
  for (i = 0; i < ext->priv->nEle; i++)
    {
      ids = &g_array_index(ext->priv->glIds, GlIds, i);
      visu_element_atomic_setUnits(VISU_ELEMENT_ATOMIC(ids->renderer), units);
    }  
}

static VisuPlane* _getBoundingPlane(VisuGlView *view, guint x1, guint y1, guint x2, guint y2)
{
  VisuPlane *plane;
  gfloat orig[3], xyz1[3], xyz2[3], uvw[3];

  visu_gl_view_getRealCoordinates(view, orig, x1, y1, 0.f);
  visu_gl_view_getRealCoordinates(view, xyz1, x2, y2, 0.f);
  visu_gl_view_getRealCoordinates(view, xyz2, x1, y1, 1.f);
  xyz1[0] -= orig[0];
  xyz1[1] -= orig[1];
  xyz1[2] -= orig[2];
  xyz2[0] -= orig[0];
  xyz2[1] -= orig[1];
  xyz2[2] -= orig[2];
  uvw[0] = xyz1[1] * xyz2[2] - xyz1[2] * xyz2[1];
  uvw[1] = xyz1[2] * xyz2[0] - xyz1[0] * xyz2[2];
  uvw[2] = xyz1[0] * xyz2[1] - xyz1[1] * xyz2[0];
  plane = visu_plane_newUndefined();
  visu_plane_setNormalVector(plane, uvw);
  visu_plane_setOrigin(plane, orig);
  visu_plane_setHiddenState(plane, VISU_PLANE_SIDE_MINUS);

  return plane;
}
/**
 * visu_gl_ext_nodes_getSelectionByRegion:
 * @ext: a #VisuGlExtNodes object;
 * @x1: a window coordinate;
 * @y1: a window coordinate;
 * @x2: a window coordinate;
 * @y2: a window coordinate.
 *
 * Get the #VisuNode ids in the picked region defined by (x1, y1) -
 * (x2, y2).
 * 
 * Since: 3.7
 *
 * Returns: (transfer full) (element-type guint): an empty list if no
 * node found, or a newly created list of ids if any.
 **/
GArray* visu_gl_ext_nodes_getSelectionByRegion(VisuGlExtNodes *ext,
                                               int x1, int y1, int x2, int y2)
{
  GArray *ids;
  VisuPlane *planeL, *planeR, *planeT, *planeB;
  VisuDataIter iter;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext) && ext->priv->dataObj, (GArray*)0);

  ids = g_array_new(FALSE, FALSE, sizeof(guint));

  planeL = _getBoundingPlane(ext->priv->view, MIN(x1, x2), MAX(y1, y2), MIN(x1, x2), MIN(y1, y2));
  planeR = _getBoundingPlane(ext->priv->view, MAX(x1, x2), MIN(y1, y2), MAX(x1, x2), MAX(y1, y2));
  planeT = _getBoundingPlane(ext->priv->view, MIN(x1, x2), MIN(y1, y2), MAX(x1, x2), MIN(y1, y2));
  planeB = _getBoundingPlane(ext->priv->view, MAX(x1, x2), MAX(y1, y2), MIN(x1, x2), MAX(y1, y2));
  
  for (visu_data_iter_new(ext->priv->dataObj, &iter, ITER_NODES_VISIBLE);
       visu_data_iter_isValid(&iter); visu_data_iter_next(&iter))
    if (visu_plane_getVisibility(planeL, iter.xyz)
        && visu_plane_getVisibility(planeR, iter.xyz)
        && visu_plane_getVisibility(planeT, iter.xyz)
        && visu_plane_getVisibility(planeB, iter.xyz))
      g_array_append_val(ids, iter.parent.node->number);

  g_object_unref(planeL);
  g_object_unref(planeR);
  g_object_unref(planeT);
  g_object_unref(planeB);

  return ids;
}

static void _labelBuf(const GArray *vertices, guint id, const gfloat rgba[4] _U_, const gfloat material[5] _U_, gpointer data)
{
  const gfloat _material[5] = {-1.f, -1.f, -1.f, -1.f, -1.f};
  gfloat color[4];
  struct _BufClosure *closure = (struct _BufClosure*)data;
  guint idx[3];
  idx[0] = (closure->iNode >> 16) % 256;
  idx[1] = (closure->iNode >> 8) % 256;
  idx[2] = (closure->iNode) % 256;

  memcpy(color, idx, sizeof(guint) * 3);
  visu_gl_ext_layoutBufferWithColor(closure->ext, closure->iBuf + id,
                                    vertices, color, _material);
}
/**
 * visu_gl_ext_nodes_getSelection:
 * @ext: a #VisuGlExtNodes object;
 * @x: a window coordinate;
 * @y: a window coordinate.
 *
 * Get the id of a #VisuNode on top of the z-buffer.
 *
 * Since: 3.7
 *
 * Returns: -1 if no node found, or its id.
 **/
int visu_gl_ext_nodes_getSelection(VisuGlExtNodes *ext, int x, int y)
{
  int number;
  GLubyte idx[3];

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext) && ext->priv->dataObj, -1);
  g_return_val_if_fail(ext->priv->frameBuf && ext->priv->renderBuf, -1);

  _drawNodes(ext, _labelBuf);

  g_debug("Visu ExtSet: pixmap export using framebuffer %d on renderbuffer %d.",
          ext->priv->frameBuf, ext->priv->renderBuf);
  glBindRenderbuffer(GL_RENDERBUFFER, ext->priv->renderBuf);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8,
                        visu_gl_view_getWidth(ext->priv->view),
                        visu_gl_view_getHeight(ext->priv->view));
  glBindRenderbuffer(GL_RENDERBUFFER, ext->priv->depthBuf);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
                        visu_gl_view_getWidth(ext->priv->view),
                        visu_gl_view_getHeight(ext->priv->view));

  glBindFramebuffer(GL_FRAMEBUFFER, ext->priv->frameBuf);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            GL_RENDERBUFFER, ext->priv->renderBuf);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                            GL_RENDERBUFFER, ext->priv->depthBuf);

  glViewport(0, 0, visu_gl_view_getWidth(ext->priv->view),
             visu_gl_view_getHeight(ext->priv->view));

  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_DITHER);

  visu_gl_ext_startRenderShader(VISU_GL_EXT(ext), SHADER_PICK, 0);
  visu_gl_ext_renderBuffers(VISU_GL_EXT(ext));
  visu_gl_ext_stopRenderShader(VISU_GL_EXT(ext), SHADER_PICK, 0);

  idx[0] = idx[1] = idx[2] = 0;
  glReadBuffer(GL_COLOR_ATTACHMENT0);
  glReadPixels(x, visu_gl_view_getHeight(ext->priv->view) - y,
               1, 1, GL_RGB, GL_UNSIGNED_BYTE, idx);
  g_debug("Extension Nodes: pick value %d %d %d.", idx[0], idx[1], idx[2]);
  
  glBindRenderbuffer(GL_RENDERBUFFER, 0);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  visu_gl_ext_setDirty(VISU_GL_EXT(ext), VISU_GL_DRAW_REQUIRED);

  number = (idx[0] * 256 + idx[1]) * 256 + idx[2];
  return number - 1;
}

static void _setColorizer(VisuNodeArrayRenderer *nodes,
                          VisuDataColorizer *colorizer)
{
  VisuGlExtNodes *self;
  g_return_if_fail(VISU_IS_GL_EXT_NODES(nodes));

  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) == colorizer)
    return;

  if (_getColorizer(nodes))
    {
      g_signal_handler_disconnect(_getColorizer(nodes), self->priv->dirtyId);
      g_object_unref(_getColorizer(nodes));
    }
  if (colorizer)
    {
      g_object_ref(colorizer);
      self->priv->dirtyId =
        g_signal_connect_swapped(colorizer, "dirty",
                                 G_CALLBACK(_setDirty), self);
      visu_sourceable_follow(VISU_SOURCEABLE(colorizer), self->priv->dataObj);
    }

  return;
}

static gboolean _pushColorizer(VisuNodeArrayRenderer *nodes,
                               VisuDataColorizer *colorizer)
{
  GList *elem;
  VisuGlExtNodes *self;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);
  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) == colorizer)
    return FALSE;

  if ((elem = g_list_find(self->priv->colorizers, colorizer)))
    {
      g_object_unref(elem->data);
      self->priv->colorizers = g_list_delete_link(self->priv->colorizers, elem);
    }
  _setColorizer(nodes, colorizer);
  self->priv->colorizers = g_list_prepend(self->priv->colorizers, colorizer);
  g_object_ref(colorizer);
  visu_gl_ext_setDirty(VISU_GL_EXT(self), VISU_GL_DRAW_REQUIRED);

  return TRUE;
}

static gboolean _removeColorizer(VisuNodeArrayRenderer *nodes,
                                 VisuDataColorizer *colorizer)
{
  GList *elem;
  VisuGlExtNodes *self;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);
  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) != colorizer)
    {
      if ((elem = g_list_find(self->priv->colorizers, colorizer)))
        {
          g_object_unref(elem->data);
          self->priv->colorizers = g_list_delete_link(self->priv->colorizers, elem);
        }
      return FALSE;
    }

  _setColorizer(nodes, self->priv->colorizers->next ? self->priv->colorizers->next->data : (VisuDataColorizer*)0);
  self->priv->colorizers = g_list_delete_link(self->priv->colorizers,
                                              self->priv->colorizers);
  g_object_unref(colorizer);
  visu_gl_ext_setDirty(VISU_GL_EXT(self), VISU_GL_DRAW_REQUIRED);

  return TRUE;
}

static VisuDataColorizer* _getColorizer(const VisuNodeArrayRenderer *nodes)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), (VisuDataColorizer*)0);

  return VISU_GL_EXT_NODES(nodes)->priv->colorizers ? VISU_GL_EXT_NODES(nodes)->priv->colorizers->data : (VisuDataColorizer*)0;
}
