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

	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 "elementAtomic.h"

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

#include <visu_configFile.h>
#include <visu_basic.h>
#include <opengl.h>
#include <openGLFunctions/objectList.h>

/**
 * SECTION:elementAtomic
 * @short_description: a class implementing rendering for
 * #VisuDataAtomic objects.
 *
 * <para>This class implements the virtual method of
 * #VisuElementRenderer class to display nodes as 0D objects at a
 * given point. The specific shape is defined by
 * #VisuElementAtomicShapeId enumeration. The size of the 0D object is
 * given by the "radius" property which is a length with a given
 * "units" property.</para>
 * <para>visu_element_atomic_getFromPool() is a specific function to
 * associate a unique #VisuElementAtomic to a given #VisuElement.</para>
 */

/**
 * VisuElementAtomicClass:
 * @parent: its parent.
 *
 * Interface for class that can represent #VisuElement of the same
 * kind in the same way (usually spheres).
 *
 * Since: 3.8
 */

#define _DEFAULT_RADIUS 1.f

#define FLAG_RESOURCE_RADIUS_SHAPE "atomic_radius_shape"
#define DESC_RESOURCE_RADIUS_SHAPE "The radius of the element and its shape, a real > 0. & [Sphere Cube Elipsoid Point]"
/* These functions write all the element list to export there associated resources. */
static void exportAtomic(GString *data, VisuData* dataObj);
#define FLAG_PARAMETER_SHAPE "atomic_sphere_method"
#define DESC_PARAMETER_SHAPE "The sphere drawing method, [GluSphere Icosahedron]"
static void exportAtomicShape(GString *data, VisuData* dataObj);

/* Read routines for the config file. */
static void onEntryRadiusShape(VisuConfigFile *object, VisuConfigFileEntry *entry, gpointer data);
static void onEntryUnit(VisuElementAtomic *ele, VisuConfigFileEntry *entry, VisuConfigFile *obj);

enum
  {
    sphere_glu,
    sphere_icosahedron,
    sphere_nb
  };
static guint _sphereMethod = sphere_glu;
static const char* _sphereName[sphere_nb + 1] = {"GluSphere", "Icosahedron", (const char*)0};

static const char* _shapeName[VISU_ELEMENT_ATOMIC_N_SHAPES + 1] =
  {"Sphere", "Cube", "Elipsoid", "Point", "Torus", (const char*)0};
static const char* _shapeNameI18n[VISU_ELEMENT_ATOMIC_N_SHAPES + 1];

/**
 * VisuElementAtomic:
 *
 * Structure used to define #VisuElementAtomic objects.
 *
 * Since: 3.8
 */
struct _VisuElementAtomicPrivate
{
  gfloat radius;
  ToolUnits units;
  VisuElementAtomicShapeId shape;
  gfloat ratio, phi, theta;

  GLuint glElement;
};

enum {
  PROP_0,
  PROP_RADIUS,
  PROP_UNITS,
  PROP_SHAPE,
  PROP_RATIO,
  PROP_PHI,
  PROP_THETA,
  N_PROPS
};
static GParamSpec *_properties[N_PROPS];

static GList *_pool;

static gboolean _sphereFromName(const gchar *name, guint *value);
static gboolean _shapeFromName(const gchar *name, VisuElementAtomicShapeId *shape);

static void visu_element_atomic_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec);
static void visu_element_atomic_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec);
static void             _compile    (VisuElementRenderer *element, const VisuGlView *view);
static void             _call       (const VisuElementRenderer *element);
static void             _callAt     (const VisuElementRenderer *element,
                                     const VisuDataColorizer *colorizer,
                                     const VisuData *data, const VisuNode *node);
static gfloat           _getExtent  (const VisuElementRenderer *self);

G_DEFINE_TYPE_WITH_CODE(VisuElementAtomic, visu_element_atomic, VISU_TYPE_ELEMENT_RENDERER,
                        G_ADD_PRIVATE(VisuElementAtomic))

static void visu_element_atomic_class_init(VisuElementAtomicClass *klass)
{
  VisuConfigFileEntry *resourceEntry;

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->set_property = visu_element_atomic_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_element_atomic_get_property;
  VISU_ELEMENT_RENDERER_CLASS(klass)->compile     = _compile;
  VISU_ELEMENT_RENDERER_CLASS(klass)->call        = _call;
  VISU_ELEMENT_RENDERER_CLASS(klass)->callAt      = _callAt;
  VISU_ELEMENT_RENDERER_CLASS(klass)->getExtent   = _getExtent;

  /**
   * VisuElementAtomic::radius:
   *
   * The atomic radius.
   *
   * Since: 3.8
   */
  _properties[PROP_RADIUS] =
    g_param_spec_float("radius", "Radius", "atomic radius",
                       0.001f, G_MAXFLOAT, _DEFAULT_RADIUS, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_RADIUS,
                                  _properties[PROP_RADIUS]);
  /**
   * VisuElementAtomic::units:
   *
   * The unit in which the radius is defined.
   *
   * Since: 3.8
   */
  _properties[PROP_UNITS] =
    g_param_spec_uint("units", "Units", "radius units",
                      0, TOOL_UNITS_N_VALUES - 1, TOOL_UNITS_UNDEFINED, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_UNITS,
                                  _properties[PROP_UNITS]);
  /**
   * VisuElementAtomic::shape:
   *
   * The shape used to represent a given element.
   *
   * Since: 3.8
   */
  _properties[PROP_SHAPE] =
    g_param_spec_uint("shape", "Shape", "atomic shape",
                      0, VISU_ELEMENT_ATOMIC_N_SHAPES - 1, VISU_ELEMENT_ATOMIC_SPHERE,
                      G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_SHAPE,
                                  _properties[PROP_SHAPE]);
  /**
   * VisuElementAtomic::elipsoid-ratio:
   *
   * The ratio used to represent an elongated shape like an elipsoid.
   *
   * Since: 3.8
   */
  _properties[PROP_RATIO] =
    g_param_spec_float("elipsoid-ratio", "Elipsoid ratio", "Elipsoid ratio",
                       0.f, G_MAXFLOAT, 1.f, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_RATIO,
                                  _properties[PROP_RATIO]);
  /**
   * VisuElementAtomic::elipsoid-angle-phi:
   *
   * The angle phi used to aligned an elongated shape like an elipsoid.
   *
   * Since: 3.8
   */
  _properties[PROP_PHI] =
    g_param_spec_float("elipsoid-angle-phi", "Elipsoid angle phi", "Elipsoid angle phi",
                       0.f, 360.f, 0.f, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_PHI,
                                  _properties[PROP_PHI]);
  /**
   * VisuElementAtomic::elipsoid-angle-theta:
   *
   * The angle tetha used to aligned an elongated shape like an elipsoid.
   *
   * Since: 3.8
   */
  _properties[PROP_THETA] =
    g_param_spec_float("elipsoid-angle-theta", "Elipsoid angle theta", "Elipsoid angle theta",
                       0.f, 180.f, 90.f, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_THETA,
                                  _properties[PROP_THETA]);

  resourceEntry = visu_config_file_addTokenizedEntry(VISU_CONFIG_FILE_RESOURCE,
                                                     FLAG_RESOURCE_RADIUS_SHAPE,
                                                     DESC_RESOURCE_RADIUS_SHAPE,
                                                     TRUE);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_RADIUS_SHAPE,
                   G_CALLBACK(onEntryRadiusShape), (gpointer)0);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE, exportAtomic);
  resourceEntry = visu_config_file_addEnumEntry(VISU_CONFIG_FILE_PARAMETER,
                                                FLAG_PARAMETER_SHAPE, DESC_PARAMETER_SHAPE,
                                                &_sphereMethod, _sphereFromName, FALSE);
  visu_config_file_entry_setVersion(resourceEntry, 3.4f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_PARAMETER,
                                     exportAtomicShape);

  _pool = (GList*)0;

  _shapeNameI18n[0] = _("Sphere");
  _shapeNameI18n[1] = _("Cube");
  _shapeNameI18n[2] = _("Elipsoid");
  _shapeNameI18n[3] = _("Point");
  _shapeNameI18n[4] = _("Torus");
  _shapeNameI18n[5] = (const char*)0;
}
static void visu_element_atomic_init(VisuElementAtomic *obj)
{
  DBG_fprintf(stderr, "Visu Pair Atomic: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_element_atomic_get_instance_private(obj);

  /* Private data. */
  obj->priv->radius            = _DEFAULT_RADIUS;
  obj->priv->units             = visu_basic_getPreferedUnit();
  obj->priv->shape             = VISU_ELEMENT_ATOMIC_SPHERE;
  obj->priv->ratio             = 1.f;
  obj->priv->phi               = 0.f;
  obj->priv->theta             = 90.f;
  obj->priv->glElement         = 0;

  g_signal_connect_object(VISU_CONFIG_FILE_PARAMETER, "parsed::main_unit",
                          G_CALLBACK(onEntryUnit), (gpointer)obj, G_CONNECT_SWAPPED);
}
static void visu_element_atomic_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec)
{
  VisuElementAtomic *self = VISU_ELEMENT_ATOMIC(obj);

  DBG_fprintf(stderr, "Visu Element Atomic: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_RADIUS:
      g_value_set_float(value, self->priv->radius);
      DBG_fprintf(stderr, "%g.\n", self->priv->radius);
      break;
    case PROP_UNITS:
      g_value_set_uint(value, self->priv->units);
      DBG_fprintf(stderr, "%d.\n", self->priv->units);
      break;
    case PROP_SHAPE:
      g_value_set_uint(value, self->priv->shape);
      DBG_fprintf(stderr, "%d.\n", self->priv->shape);
      break;
    case PROP_RATIO:
      g_value_set_float(value, self->priv->ratio);
      DBG_fprintf(stderr, "%g.\n", self->priv->ratio);
      break;
    case PROP_PHI:
      g_value_set_float(value, self->priv->phi);
      DBG_fprintf(stderr, "%g.\n", self->priv->phi);
      break;
    case PROP_THETA:
      g_value_set_float(value, self->priv->theta);
      DBG_fprintf(stderr, "%g.\n", self->priv->theta);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_element_atomic_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec)
{
  VisuElementAtomic *self = VISU_ELEMENT_ATOMIC(obj);

  DBG_fprintf(stderr, "Visu Element Atomic: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_RADIUS:
      DBG_fprintf(stderr, "%g.\n", self->priv->radius);
      visu_element_atomic_setRadius(self, g_value_get_float(value));
      break;
    case PROP_UNITS:
      DBG_fprintf(stderr, "%d.\n", g_value_get_uint(value));
      visu_element_atomic_setUnits(self, g_value_get_uint(value));
      break;
    case PROP_SHAPE:
      visu_element_atomic_setShape(self, g_value_get_uint(value));
      DBG_fprintf(stderr, "%d.\n", self->priv->shape);
      break;
    case PROP_RATIO:
      visu_element_atomic_setElipsoidRatio(self, g_value_get_float(value));
      DBG_fprintf(stderr, "%g.\n", self->priv->ratio);
      break;
    case PROP_PHI:
      visu_element_atomic_setElipsoidPhi(self, g_value_get_float(value));
      DBG_fprintf(stderr, "%g.\n", self->priv->phi);
      break;
    case PROP_THETA:
      visu_element_atomic_setElipsoidTheta(self, g_value_get_float(value));
      DBG_fprintf(stderr, "%g.\n", self->priv->theta);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_element_atomic_new:
 * @element: a #VisuElement object.
 *
 * Creates a new #VisuElementAtomic object used to render @element.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuElementAtomic object.
 **/
VisuElementAtomic* visu_element_atomic_new(VisuElement *element)
{
  return VISU_ELEMENT_ATOMIC(g_object_new(VISU_TYPE_ELEMENT_ATOMIC, "element", element, NULL));
}
/**
 * visu_element_atomic_getFromPool:
 * @element: a #VisuElement object.
 *
 * Retrieve a #VisuElementAtomic representing @element. This
 * #VisuElementAtomic is unique and its parent properties are bound to
 * the unique #VisuElementRenderer for @element.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuElementAtomic for @element.
 **/
VisuElementAtomic* visu_element_atomic_getFromPool(VisuElement *element)
{
  GList *lst;
  VisuElementAtomic *atomic;

  for (lst = _pool; lst; lst = g_list_next(lst))
    if (visu_element_renderer_getElement(VISU_ELEMENT_RENDERER(lst->data)) == element)
      return VISU_ELEMENT_ATOMIC(lst->data);
  
  atomic = visu_element_atomic_new(element);
  visu_element_renderer_bindToPool(VISU_ELEMENT_RENDERER(atomic));
  _pool = g_list_prepend(_pool, atomic);  
  return atomic;
}
/**
 * visu_element_atomic_bindToPool:
 * @atomic: a #VisuElementAtomic object.
 *
 * Bind all properties of @atomic to the #VisuElementAtomic object
 * corresponding to the same #VisuElement from the pool. The binding
 * is bidirectional. This method is usefull to create a pool of
 * objects inheriting from #VisuElementAtomic.
 *
 * Since: 3.8
 **/
void visu_element_atomic_bindToPool(VisuElementAtomic *atomic)
{
  VisuElementAtomic *pool;

  visu_element_renderer_bindToPool(VISU_ELEMENT_RENDERER(atomic));
  pool = visu_element_atomic_getFromPool(visu_element_renderer_getElement(VISU_ELEMENT_RENDERER(atomic)));
  g_object_bind_property(pool, "radius", atomic, "radius",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "units", atomic, "units",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "shape", atomic, "shape",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "elipsoid-ratio", atomic, "elipsoid-ratio",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "elipsoid-angle-phi", atomic, "elipsoid-angle-phi",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "elipsoid-angle-theta", atomic, "elipsoid-angle-theta",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
}
/**
 * visu_element_atomic_pool_finalize: (skip)
 *
 * Destroy the internal list of known #VisuElementAtomic objects, see
 * visu_element_atomic_getFromPool().
 *
 * Since: 3.8
 **/
void visu_element_atomic_pool_finalize(void)
{
  g_list_free_full(_pool, (GDestroyNotify)g_object_unref);
  _pool = (GList*)0;
}

static gfloat _getExtent(const VisuElementRenderer *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), 0.f);

  return VISU_ELEMENT_ATOMIC(self)->priv->radius;
}
/**
 * visu_element_atomic_getRadius:
 * @self: a #VisuElementAtomic object.
 *
 * Retrieve the radius used to draw @self. The unit of the value is
 * given by visu_element_atomic_getUnits().
 *
 * Since: 3.8
 *
 * Returns: a radius value.
 **/
gfloat visu_element_atomic_getRadius(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), 0.f);

  return self->priv->radius;
}
/**
 * visu_element_atomic_setRadius:
 * @self: a #VisuElementAtomic object.
 * @val: a positive float value.
 *
 * Change the radius (or long axe) of the representation of @self.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setRadius(VisuElementAtomic *self, gfloat val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->radius == val)
    return FALSE;

  DBG_fprintf(stderr, "Element Atomic: changing radius from %g to %g.\n", self->priv->radius, val);
  self->priv->radius = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_RADIUS]);
  g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));

  _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}
/**
 * visu_element_atomic_getUnits:
 * @self: a #VisuElementAtomic object.
 *
 * The units in which the radius value is given.
 *
 * Since: 3.8
 *
 * Returns: a unit.
 **/
ToolUnits visu_element_atomic_getUnits(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), TOOL_UNITS_UNDEFINED);

  return self->priv->units;
}
/**
 * visu_element_atomic_setUnits:
 * @self: a #VisuElementAtomic object.
 * @val: a #ToolUnits value.
 *
 * Change the unit in wich the radius is given, see
 * visu_element_atomic_setRadius().
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setUnits(VisuElementAtomic *self, ToolUnits val)
{
  ToolUnits unit_;
  double fact;

  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->units == val)
    return FALSE;

  unit_ = self->priv->units;
  self->priv->units = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_UNITS]);

  if (unit_ == TOOL_UNITS_UNDEFINED || val == TOOL_UNITS_UNDEFINED)
    return TRUE;

  fact = (double)tool_physic_getUnitValueInMeter(unit_) /
    tool_physic_getUnitValueInMeter(val);

  self->priv->radius *= fact;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_RADIUS]);

  _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}
/**
 * visu_element_atomic_getShape:
 * @self: a #VisuElementAtomic object.
 *
 * Retrieve the #VisuElementAtomicShapeId that @self is using for representation.
 *
 * Since: 3.8
 *
 * Returns: the shape used by @self.
 **/
VisuElementAtomicShapeId visu_element_atomic_getShape(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), VISU_ELEMENT_ATOMIC_SPHERE);

  return self->priv->shape;
}
/**
 * visu_element_atomic_setShape:
 * @self: a #VisuElementAtomic object.
 * @val: a #VisuElementAtomicShapeId value.
 *
 * Change the representation shape of @self.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setShape(VisuElementAtomic *self,
                                      VisuElementAtomicShapeId val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->shape == val)
    return FALSE;

  self->priv->shape = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_SHAPE]);

  _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}
/**
 * visu_element_atomic_getElipsoidRatio:
 * @self: a #VisuElementAtomic object.
 *
 * When @self is used to draw constant elipsoid or torus, this value
 * is used to adjust the ratio between the two angles.
 *
 * Since: 3.8
 *
 * Returns: a ratio value.
 **/
gfloat visu_element_atomic_getElipsoidRatio(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), 1.f);

  return self->priv->ratio;
}
/**
 * visu_element_atomic_setElipsoidRatio:
 * @self: a #VisuElementAtomic object.
 * @val: a positive float value.
 *
 * Change the ratio between the long axe and the short axe of the
 * representation for shapes that are not isotropic.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setElipsoidRatio(VisuElementAtomic *self, gfloat val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->ratio == val)
    return FALSE;

  self->priv->ratio = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_RATIO]);

  if (self->priv->shape == VISU_ELEMENT_ATOMIC_ELLIPSOID ||
      self->priv->shape == VISU_ELEMENT_ATOMIC_TORUS)
    _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}
/**
 * visu_element_atomic_getElipsoidPhi:
 * @self: a #VisuElementAtomic object.
 *
 * Retrieve the phi angle used to draw elipsoid shape with this renderer.
 *
 * Since: 3.8
 *
 * Returns: the phi angle.
 **/
gfloat visu_element_atomic_getElipsoidPhi(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), 0.f);

  return self->priv->phi;
}
/**
 * visu_element_atomic_setElipsoidPhi:
 * @self: a #VisuElementAtomic object.
 * @val: a float value.
 *
 * Change the phi angle of the representation for shapes that are not isotropic.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setElipsoidPhi(VisuElementAtomic *self, gfloat val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->phi == val)
    return FALSE;

  self->priv->phi = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_PHI]);

  if (self->priv->shape == VISU_ELEMENT_ATOMIC_ELLIPSOID ||
      self->priv->shape == VISU_ELEMENT_ATOMIC_TORUS)
    _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}
/**
 * visu_element_atomic_getElipsoidTheta:
 * @self: a #VisuElementAtomic object.
 *
 * Retrieve the theta angle used to draw elipsoid shape with this renderer.
 *
 * Since: 3.8
 *
 * Returns: a theta value.
 **/
gfloat visu_element_atomic_getElipsoidTheta(const VisuElementAtomic *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), 90.f);

  return self->priv->theta;
}
/**
 * visu_element_atomic_setElipsoidTheta:
 * @self: a #VisuElementAtomic object.
 * @val: a float value.
 *
 * Change the theta angle of the representation for shapes that are not isotropic.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_element_atomic_setElipsoidTheta(VisuElementAtomic *self, gfloat val)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_ATOMIC(self), FALSE);

  if (self->priv->theta == val)
    return FALSE;

  self->priv->theta = val;
  g_object_notify_by_pspec(G_OBJECT(self), _properties[PROP_THETA]);

  if (self->priv->shape == VISU_ELEMENT_ATOMIC_ELLIPSOID ||
      self->priv->shape == VISU_ELEMENT_ATOMIC_TORUS)
    _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
  return TRUE;
}

/* The icosahedron drawing. */
#define X .525731112119133606 
#define Z .850650808352039932
static GLfloat vdata[12][3] = {    
   {X, 0.0, -Z}, {-X, 0.0, -Z}, {X, 0.0, Z}, {-X, 0.0, Z},    
   {0.0, -Z, -X}, {0.0, -Z, X}, {0.0, Z, -X}, {0.0, Z, X},    
   {-Z, -X, 0.0}, {Z, -X, 0.0}, {-Z, X, 0.0}, {Z, X, 0.0} 
};
static GLuint tindices[20][3] = { 
   {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},    
   {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},    
   {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6}, 
   {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
static void drawtriangle(float *v1, float *v2, float *v3) 
{ 
   glBegin(GL_TRIANGLES); 
      glNormal3fv(v1); glVertex3fv(v1);    
      glNormal3fv(v2); glVertex3fv(v2);    
      glNormal3fv(v3); glVertex3fv(v3);    
   glEnd(); 
}
static void normalize(float v[3]) {    
   GLfloat d = 1.f / sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); 

   g_return_if_fail(d > 0.);
   v[0] *= d; v[1] *= d; v[2] *= d; 
}
static void subdivide(float *v1, float *v2, float *v3, int depth) 
{ 
  GLfloat v12[3], v23[3], v31[3];    
  GLint i;

  if (depth == 0)
    {
      drawtriangle(v1, v2, v3);
      return;
    }
  for (i = 0; i < 3; i++)
    { 
      v12[i] = v1[i]+v2[i]; 
      v23[i] = v2[i]+v3[i];     
      v31[i] = v3[i]+v1[i];    
    } 
  normalize(v12);    
  normalize(v23); 
  normalize(v31); 
  subdivide(v1, v12, v31, depth - 1);    
  subdivide(v2, v23, v12, depth - 1);    
  subdivide(v3, v31, v23, depth - 1);    
  subdivide(v12, v23, v31, depth - 1); 
}
static void _renderAtomic(const VisuElementAtomic *ele, int nlat, gfloat gross)
{
  int i, nfac;
  GLUquadricObj *obj;

  obj = gluNewQuadric();
  switch (ele->priv->shape)
    {
    case VISU_ELEMENT_ATOMIC_SPHERE:
      DBG_fprintf(stderr, " | use sphere method %d\n", _sphereMethod);
      if (_sphereMethod == sphere_glu)
        gluSphere(obj, (double)ele->priv->radius, nlat, nlat);
      else if (_sphereMethod == sphere_icosahedron)
        {
          nfac = (int)(log((float)(nlat + 2) / 4.f) / log(2.f));
          DBG_fprintf(stderr, " | glusphere vs. icosahedron %dx%d\n",
                      nlat * nlat, 20 * (int)pow(4, nfac));
          glPushMatrix();
          glScalef(ele->priv->radius, ele->priv->radius, ele->priv->radius);
          glBegin(GL_TRIANGLES);    
          for (i = 0; i < 20; i++)
            subdivide(&vdata[tindices[i][0]][0],       
                      &vdata[tindices[i][1]][0],       
                      &vdata[tindices[i][2]][0], nfac);
          glEnd();
          glPopMatrix();
        }
      else
        g_warning("Wrong sphere method.");
      break;
    case VISU_ELEMENT_ATOMIC_ELLIPSOID:
      glPushMatrix();
      glRotatef(ele->priv->phi, 0., 0., 1.);
      glRotatef(ele->priv->theta, 0., 1., 0.);
      glScalef(1.0, 1.0, ele->priv->ratio);
      gluSphere(obj, (double)ele->priv->radius, nlat, nlat);
      glPopMatrix();
      break;
    case VISU_ELEMENT_ATOMIC_POINT:
      glPointSize(MAX(1, (int)(ele->priv->radius * gross * 5.)));
      glBegin(GL_POINTS);
      glVertex3f(0., 0., 0.);
      glEnd();
      break;
    case VISU_ELEMENT_ATOMIC_CUBE:
      glBegin(GL_QUADS);

      glNormal3f(0., 0., 1.);
      glVertex3f(ele->priv->radius / 2., ele->priv->radius / 2., ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2., ele->priv->radius / 2., ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2., -ele->priv->radius / 2., ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2., -ele->priv->radius / 2., ele->priv->radius / 2.);

      glNormal3f(0., 0., -1.);
      glVertex3f(ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);

      glNormal3f(1., 0., 0.);
      glVertex3f(ele->priv->radius / 2.,ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,-ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);

      glNormal3f(-1., 0., 0.);
      glVertex3f(-ele->priv->radius / 2.,ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,-ele->priv->radius / 2.,ele->priv->radius / 2.);

      glNormal3f(0., 1., 0.);
      glVertex3f(-ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,ele->priv->radius / 2.,-ele->priv->radius / 2.);

      glNormal3f(0., -1., 0.);
      glVertex3f(-ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,-ele->priv->radius / 2.,-ele->priv->radius / 2.);
      glVertex3f(ele->priv->radius / 2.,-ele->priv->radius / 2.,ele->priv->radius / 2.);
      glVertex3f(-ele->priv->radius / 2.,-ele->priv->radius / 2.,ele->priv->radius / 2.);

      glEnd();
      break;
    case VISU_ELEMENT_ATOMIC_TORUS:
      
      glPushMatrix();
      glRotatef(ele->priv->phi, 0., 0., 1.);
      glRotatef(ele->priv->theta, 0., 1., 0.);
      visu_gl_drawTorus(obj, ele->priv->radius, ele->priv->ratio, nlat, nlat, NULL);
      glPopMatrix();
      
      break;
    default:
      g_warning("Unsupported shape id.");
    }
  gluDeleteQuadric(obj);
}
static void _compile(VisuElementRenderer *element, const VisuGlView *view)
{
  int nlat;
  VisuElementAtomic *ele;

  ele = VISU_ELEMENT_ATOMIC(element);
  if (ele->priv->glElement)
    glDeleteLists(ele->priv->glElement, 1);

  if (!view)
    return;

  nlat = visu_gl_view_getDetailLevel(view, ele->priv->radius);
  DBG_fprintf(stderr, "Rendering Atomic: creating '%s' for %s (%d - fac %d)\n",
              _shapeName[ele->priv->shape],
              visu_element_getName(visu_element_renderer_getConstElement(element)),
              visu_element_renderer_getConstElement(element)->typeNumber, nlat);
  if (nlat < 0)
    return;

  if (!ele->priv->glElement)
    ele->priv->glElement = visu_gl_objectlist_new(1);

  glNewList(ele->priv->glElement, GL_COMPILE);
  _renderAtomic(ele, nlat, view->camera.gross);
  glEndList();
}
static void _call(const VisuElementRenderer *element)
{
  VisuElementAtomic *ele;

  ele = VISU_ELEMENT_ATOMIC(element);
  g_return_if_fail(ele->priv->glElement);

  glCallList(ele->priv->glElement);
}
static void _callAt(const VisuElementRenderer *element,
                    const VisuDataColorizer *colorizer,
                    const VisuData *data, const VisuNode *node)
{
  float rgba[4];
  float xyz[3];
  float scale;

  if (colorizer && visu_data_colorizer_getColor(colorizer, rgba, data, node))
    visu_gl_setColor((VisuGl*)0, visu_element_renderer_getMaterial(element), rgba);
  else if (colorizer && visu_data_colorizer_getActive(colorizer))
    visu_element_renderer_colorize(element, VISU_ELEMENT_RENDERER_NO_EFFECT);

  visu_data_getNodePosition(data, node, xyz);
  scale = (colorizer) ? visu_data_colorizer_getScalingFactor(colorizer, data, node) : 1.f;

  glPushMatrix();
  glTranslated(xyz[0], xyz[1], xyz[2]);
  glScalef(scale, scale, scale);
  glCallList(VISU_ELEMENT_ATOMIC(element)->priv->glElement);
  glPopMatrix();
}

/**
 * visu_element_atomic_getShapeNames:
 * @asLabel: a boolean.
 *
 * Get the string defining #VisuElementAtomicShapeId. If @asLabel is
 * %TRUE, then the string are translated and stored in UTF8.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (array zero-terminated=1): strings
 * representing #VisuElementAtomicShapeId.
 **/
const gchar ** visu_element_atomic_getShapeNames(gboolean asLabel)
{
  if (asLabel)
    return _shapeNameI18n;
  else
    return _shapeName;
}

/*****************************************/
/* Dealing with parameters and resources */
/*****************************************/
static gboolean _sphereFromName(const gchar *name, guint *value)
{
  g_return_val_if_fail(name && value, FALSE);

  for (*value = 0; *value < sphere_nb; *value += 1)
    if (!strcmp(name, _sphereName[*value]))
      return TRUE;
  return FALSE;
}
static gboolean _shapeFromName(const gchar *name, VisuElementAtomicShapeId *shape)
{
  g_return_val_if_fail(name && shape, FALSE);

  for (*shape = 0; *shape < VISU_ELEMENT_ATOMIC_N_SHAPES; *shape += 1)
    if (!strcmp(name, _shapeName[*shape]))
      return TRUE;
  return FALSE;
}
static VisuElementAtomic* _fromEntry(VisuConfigFileEntry *entry)
{
  const gchar *label;
  VisuElement *ele;

  label = visu_config_file_entry_getLabel(entry);
  ele = visu_element_retrieveFromName(label, (gboolean*)0);
  if (!ele)
    {
      visu_config_file_entry_setErrorMessage(entry, _("'%s' wrong element name"), label);
      return (VisuElementAtomic*)0;
    }
  return visu_element_atomic_getFromPool(ele);
}
static void onEntryRadiusShape(VisuConfigFile *obj _U_,
                               VisuConfigFileEntry *entry, gpointer data _U_)
{
  VisuElementAtomic *ele;
  float rgRadius[2] = {0.f, G_MAXFLOAT};
  float radius;
  VisuElementAtomicShapeId shape;

  DBG_fprintf(stderr, "Rendering Atomic: parse line.\n");
  /* Get the element. */
  ele = _fromEntry(entry);
  if (!ele)
    return;

  /* Read 1 float. */
  if (!visu_config_file_entry_popTokenAsFloat(entry, 1, &radius, rgRadius))
    return;

  /* Read 1 string. */
  if (!visu_config_file_entry_popTokenAsEnum(entry, &shape, _shapeFromName))
    return;

  DBG_fprintf(stderr, "Rendering Atomic: store values.\n");
  visu_element_atomic_setUnits(ele, TOOL_UNITS_UNDEFINED);
  visu_element_atomic_setRadius(ele, radius);
  visu_element_atomic_setUnits(ele, visu_basic_getPreferedUnit());
  visu_element_atomic_setShape(ele, shape);
}
static void onEntryUnit(VisuElementAtomic *ele,
                        VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  if (ele->priv->units != TOOL_UNITS_UNDEFINED)
    return;

  visu_element_atomic_setUnits(ele, visu_basic_getPreferedUnit());
}
/* These functions write all the element list to export there associated resources. */
static void exportAtomic(GString *data, VisuData *dataObj)
{
  GList *pos;
  VisuElementAtomic *ele;
  const VisuElement *element;

  visu_config_file_exportComment(data, DESC_RESOURCE_RADIUS_SHAPE);
  for (pos = _pool; pos; pos = g_list_next(pos))
    {
      ele = VISU_ELEMENT_ATOMIC(pos->data);
      element = visu_element_renderer_getConstElement(VISU_ELEMENT_RENDERER(ele));
      if (!dataObj || visu_node_array_containsElement(VISU_NODE_ARRAY(dataObj), element))
        visu_config_file_exportEntry(data, FLAG_RESOURCE_RADIUS_SHAPE,
                                     visu_element_getName(element),
                                     "%10.3f %s", ele->priv->radius,
                                     _shapeName[ele->priv->shape]);
    }
  visu_config_file_exportComment(data, "");
}

static void exportAtomicShape(GString *data, VisuData* dataObj _U_)
{
  g_string_append_printf(data, "# %s\n", DESC_PARAMETER_SHAPE);
  g_string_append_printf(data, "%s: %s\n\n", FLAG_PARAMETER_SHAPE,
			 _sphereName[_sphereMethod]);
}
