/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

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

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

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

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	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 <math.h>

#include <visu_tools.h>
#include <visu_configFile.h>
#include <visu_data.h>
#include <iface_animatable.h>
#include <coreTools/toolMatrix.h>


#include "view.h"

/**
 * SECTION:view
 * @short_description: Defines all necessary informations for the
 * rendering of a view.
 *
 * <para>The #VisuGlView stores the informations for the
 * position and orientation of the camera (#ToolGlCamera).</para>
 *
 * <para>One resource is used by this part, defining the precision
 * desired by the user when drawing OpenGL objects. This precision can
 * be changed using visu_gl_view_setPrecision() and all V_Sim part
 * drawing something should use visu_gl_view_getDetailLevel() to
 * know the size of the vertices to be drawn depending on this
 * precision and the level of zoom.</para>
 *
 * <para>The rendering is done in an OpenGl viewport whose size is
 * given by the bounding box (plus 10%). The camera can be positionned
 * with three angles (theta, phi and omega) and has a zoom factor
 * (gross) and a perspective value (d_red). The angle theta is around
 * the z axis (box coordinates), phi is around the new x axis (after
 * the theta rotation) and omega is a rotation around the axis which
 * goes from the observer to the center of the bounding box. By
 * default the camera looks at the center of the bounding box but this
 * can be changed with the Xs and Ys parameters. These values are
 * stored and are readable through the #ToolGlCamera structure. They
 * must be changed with the following methods to ensure propery
 * signals are correctly emitted:
 * visu_gl_view_setThetaPhiOmega(), visu_gl_view_setGross(),
 * visu_gl_view_setPersp() and visu_gl_view_setXsYs().</para>
 */

/* Global value used as default when creating a new VisuGlView. */
static float anglesDefault[3] = {40., -50., 0.};
static float translatDefault[2] = {0.5, 0.5};
static float grossDefault = 1.;
static float perspDefault = 5.;

/***************************/
/* The #VisuGlView object. */
/***************************/
enum {
  CHANGED_SIGNAL,
  WIDTH_HEIGHT_CHANGED_SIGNAL,
  REF_LENGTH_CHANGED_SIGNAL,
  FACETTES_CHANGED_SIGNAL,
  LAST_SIGNAL
};
static guint _signals[LAST_SIGNAL] = { 0 };
enum
  {
    PROP_0,
    THETA_PROP,
    PHI_PROP,
    OMEGA_PROP,
    XS_PROP,
    YS_PROP,
    GROSS_PROP,
    PERSP_PROP,
    PRECISION_PROP,
    N_PROP,
    ADJUST_PROP,
    BOX_PROP
  };
static GParamSpec *properties[N_PROP];

struct _VisuGlViewPrivate
{
  guint width, height;
  ToolGlMatrix M;
  ToolGlMatrix P;

  VisuBox *box;
  gulong box_signal, unit_signal, bc_signal;

  gboolean adjust;
  float precision;

  VisuAnimation *theta_anim, *phi_anim, *gross_anim, *persp_anim;
  GHashTable *animations;
  
  gboolean dispose_has_run;
};

/* This is a positive float that enable to increase or
   decrease the rendering load by modifying the
   number of facettes. */
#define FLAG_PARAMETER_OPENGL_DETAILS   "opengl_details"
#define DESC_PARAMETER_OPENGL_DETAILS   "Give a value to the quality of rendering (100 is normal) ; positive integer"
static float _defaultDetails = 100.f;
static void exportParametersVisuGlView(GString *data,
                                       VisuData *dataObj);

#define FLAG_RESOURCE_OPENGL_ANGLES "opengl_theta_phi_omega"
#define DESC_RESOURCE_OPENGL_ANGLES "3 real values (degrees) for user orientation with respect to sample"

#define FLAG_RESOURCE_OPENGL_TRANSLAT "opengl_xs_ys"
#define DESC_RESOURCE_OPENGL_TRANSLAT "2 real values for image position with respect to [0.0, 1.0]x[0.0, 1.0] window"

#define FLAG_RESOURCE_OPENGL_GROSS "opengl_gross"
#define DESC_RESOURCE_OPENGL_GROSS "gross factor (must be real > 0.0)"

#define FLAG_RESOURCE_OPENGL_PERSP "opengl_d_red"
#define DESC_RESOURCE_OPENGL_PERSP "reduced perspective distance (must be real > 1.0)"

#define FLAG_PARAMETER_AUTO_ADJUST "config_autoAdjustCamera"
#define DESC_PARAMETER_AUTO_ADJUST "Auto adjust zoom capability for the box to be full size at zoom level 1 ; boolean 0 or 1"
static gboolean autoAdjustDefault = TRUE;

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

static void visu_gl_view_dispose     (GObject* obj);
static void visu_gl_view_finalize    (GObject* obj);
static void visu_gl_view_get_property(GObject* obj, guint property_id,
                                      GValue *value, GParamSpec *pspec);
static void visu_gl_view_set_property(GObject* obj, guint property_id,
                                      const GValue *value, GParamSpec *pspec);
static void visu_boxed_interface_init(VisuBoxedInterface *iface);
static void visu_animatable_interface_init(VisuAnimatableInterface *iface);

/* Callbacks. */
static void onSizeChanged(VisuBox *box, gfloat extens, gpointer data);
static void onUnitChanged(VisuGlView *view, gfloat fact);
static void onBoundaryChanged(VisuBox *box, GParamSpec *pspec, gpointer data);
static void onEntryAngles(VisuGlView *view, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryTrans(VisuGlView *view, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryGross(VisuGlView *view, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryPersp(VisuGlView *view, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryPrecision(VisuGlView *view, VisuConfigFileEntry *entry, VisuConfigFile *obj);

/* Local methods. */
static void _setBoundary(VisuGlView *view, VisuBox *box);
static VisuBox* _getBox(VisuBoxed *boxed);
static gboolean _setBox(VisuBoxed *boxed, VisuBox* box);
static VisuAnimation* _getAnimation(const VisuAnimatable *animatable, const gchar *prop);

G_DEFINE_TYPE_WITH_CODE(VisuGlView, visu_gl_view, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuGlView)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_BOXED,
                                              visu_boxed_interface_init)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_ANIMATABLE,
                                              visu_animatable_interface_init))

static void visu_gl_view_class_init(VisuGlViewClass *klass)
{
  float rg[2] = {-G_MAXFLOAT, G_MAXFLOAT};
  float rgGross[2] = {0.02f, 999.f};
  float rgPersp[2] = {1.1f, 100.f};
  float rgDetails[2] = {0.f, 500.f};
  VisuConfigFileEntry *resourceEntry;

  g_debug("Visu GlView: creating the class of the object.");
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose      = visu_gl_view_dispose;
  G_OBJECT_CLASS(klass)->finalize     = visu_gl_view_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_view_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_view_get_property;

  g_debug("                - adding new signals ;");
  /**
   * VisuGlView::changed:
   * @view: the object which emits the signal ;
   *
   * Gets emitted when the view is changed.
   *
   * Since: 3.8
   */
  _signals[CHANGED_SIGNAL] =
    g_signal_new("changed", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);

  /**
   * VisuGlView::WidthHeightChanged:
   * @view: the object which received the signal ;
   *
   * Gets emitted when the viewing frame has been changed.
   *
   * Since: 3.2
   */
  _signals[WIDTH_HEIGHT_CHANGED_SIGNAL] =
    g_signal_new("WidthHeightChanged", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);
  /**
   * VisuGlView::RefLengthChanged:
   * @view: the object which received the signal ;
   *
   * Gets emitted when the reference length of the camera has been changed.
   *
   * Since: 3.7
   */
  _signals[REF_LENGTH_CHANGED_SIGNAL] =
    g_signal_new("RefLengthChanged", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);
  /**
   * VisuGlView::DetailLevelChanged:
   * @view: the object which received the signal ;
   *
   * Gets emitted when precision of the drawn object has been changed.
   *
   * Since: 3.2
   */
  _signals[FACETTES_CHANGED_SIGNAL] =
    g_signal_new("DetailLevelChanged", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);

  /**
   * VisuGlView::theta:
   *
   * The theta angle of the camera.
   *
   * Since: 3.8
   */
  properties[THETA_PROP] = g_param_spec_double("theta", "Theta", "theta angle",
                                               -360., 360., 90., G_PARAM_READWRITE);
  /**
   * VisuGlView::phi:
   *
   * The phi angle of the camera.
   *
   * Since: 3.8
   */
  properties[PHI_PROP] = g_param_spec_double("phi", "Phi", "phi angle",
                                            -360., 360., 0., G_PARAM_READWRITE);
  /**
   * VisuGlView::omega:
   *
   * The omega angle of the camera.
   *
   * Since: 3.8
   */
  properties[OMEGA_PROP] = g_param_spec_double("omega", "Omega", "omega angle",
                                              -360., 360., 0., G_PARAM_READWRITE);
  /**
   * VisuGlView::trans-x:
   *
   * The translation of the object along the window x axis.
   *
   * Since: 3.8
   */
  properties[XS_PROP] = g_param_spec_double("trans-x", "X translation", "translation along x",
                                           -3., +3., 0.5, G_PARAM_READWRITE);
  /**
   * VisuGlView::trans-y:
   *
   * The translation of the object along the window y axis.
   *
   * Since: 3.8
   */
  properties[YS_PROP] = g_param_spec_double("trans-y", "Y translation", "translation along y",
                                           -3., +3., 0.5, G_PARAM_READWRITE);
  /**
   * VisuGlView::zoom:
   *
   * The magnification of the camera.
   *
   * Since: 3.8
   */
  properties[GROSS_PROP] = g_param_spec_double("zoom", "Zoom", "zoom level",
                                              .02, 999., 1., G_PARAM_READWRITE);
  /**
   * VisuGlView::perspective:
   *
   * The perspective of the camera.
   *
   * Since: 3.8
   */
  properties[PERSP_PROP] = g_param_spec_double("perspective", "Perspective",
                                              "perspective level",
                                              1.1, 100., 5., G_PARAM_READWRITE);
  /**
   * VisuGlView::precision:
   *
   * The accuracy of the rendering.
   *
   * Since: 3.8
   */
  properties[PRECISION_PROP] = g_param_spec_float("precision", "Precision",
                                                  "precision level",
                                                  0.f, 10.f, 1.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), ADJUST_PROP, "auto-adjust");
  g_object_class_override_property(G_OBJECT_CLASS(klass), BOX_PROP, "box");

  g_debug("                - adding resources ;");
  /* Parameters */
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_PARAMETER,
                                                      FLAG_PARAMETER_OPENGL_DETAILS,
                                                      DESC_PARAMETER_OPENGL_DETAILS,
                                                      1, &_defaultDetails, rgDetails, FALSE);
  resourceEntry = visu_config_file_addBooleanEntry(VISU_CONFIG_FILE_PARAMETER,
                                                   FLAG_PARAMETER_AUTO_ADJUST,
                                                   DESC_PARAMETER_AUTO_ADJUST,
                                                   &autoAdjustDefault, FALSE);
  visu_config_file_entry_setVersion(resourceEntry, 3.6f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_PARAMETER,
                                     exportParametersVisuGlView);

  /* Resources */
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_RESOURCE_OPENGL_ANGLES,
                                                      DESC_RESOURCE_OPENGL_ANGLES,
                                                      3, anglesDefault, rg, FALSE);
  visu_config_file_entry_setVersion(resourceEntry, 3.1f);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_RESOURCE_OPENGL_TRANSLAT,
                                                      DESC_RESOURCE_OPENGL_TRANSLAT,
                                                      2, translatDefault, rg, FALSE);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_RESOURCE_OPENGL_GROSS,
                                                      DESC_RESOURCE_OPENGL_GROSS,
                                                      1, &grossDefault, rgGross, FALSE);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_RESOURCE_OPENGL_PERSP,
                                                      DESC_RESOURCE_OPENGL_PERSP,
                                                      1, &perspDefault, rgPersp, FALSE);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     exportResourcesVisuGlView);
}
static void visu_boxed_interface_init(VisuBoxedInterface *iface)
{
  iface->get_box = _getBox;
  iface->set_box = _setBox;
}
static void visu_animatable_interface_init(VisuAnimatableInterface *iface)
{
  iface->get_animation = _getAnimation;
}

static void visu_gl_view_init(VisuGlView *view)
{
  g_debug("Visu GlView: initializing a new object (%p).",
	      (gpointer)view);

  view->priv = visu_gl_view_get_instance_private(view);
  view->priv->dispose_has_run = FALSE;
  
  view->priv->box             = (VisuBox*)0;
  view->priv->box_signal      = 0;
  view->priv->unit_signal     = 0;
  view->priv->bc_signal       = 0;
  view->priv->adjust          = autoAdjustDefault;
  view->priv->precision       = _defaultDetails / 100.f;
  view->priv->animations      = g_hash_table_new(g_str_hash, g_str_equal);
  view->priv->theta_anim      = visu_animation_new(G_OBJECT(view), "theta");
  g_hash_table_insert(view->priv->animations, "theta", view->priv->theta_anim);
  view->priv->phi_anim        = visu_animation_new(G_OBJECT(view), "phi");
  g_hash_table_insert(view->priv->animations, "phi", view->priv->phi_anim);
  view->priv->gross_anim      = visu_animation_new(G_OBJECT(view), "zoom");
  g_hash_table_insert(view->priv->animations, "zoom", view->priv->gross_anim);
  view->priv->persp_anim      = visu_animation_new(G_OBJECT(view), "perspective");
  g_hash_table_insert(view->priv->animations, "perspective", view->priv->persp_anim);

  view->priv->width     = 0;
  view->priv->height    = 0;
  view->priv->P.c[0][0] = 0.;
  tool_gl_matrix_setIdentity(&view->priv->M);

  view->camera.theta   = anglesDefault[0];
  view->camera.phi     = anglesDefault[1];
  view->camera.omega   = anglesDefault[2];
  view->camera.xs      = translatDefault[0];
  view->camera.ys      = translatDefault[1];
  view->camera.gross   = grossDefault;
  view->camera.d_red   = perspDefault;
  view->camera.length0 = -1.;
  view->camera.extens  = 0.;
  view->camera.unit    = TOOL_UNITS_UNDEFINED;
  view->camera.upAxis  = TOOL_XYZ_Z;
  view->camera.centre[0] = 0.f;
  view->camera.centre[1] = 0.f;
  view->camera.centre[2] = 0.f;

  g_debug(" | theta = %g", view->camera.theta);
  g_debug(" | phi   = %g", view->camera.phi);
  g_debug(" | omega = %g", view->camera.omega);
  g_debug(" | dx-dy = %g %g", view->camera.xs, view->camera.ys);
  g_debug(" | gross = %g", view->camera.gross);
  g_debug(" | persp = %g", view->camera.d_red);
  g_debug(" | width x height = %d x %d", view->priv->width, view->priv->height);

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_OPENGL_ANGLES,
                          G_CALLBACK(onEntryAngles), (gpointer)view, G_CONNECT_SWAPPED);
  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_OPENGL_TRANSLAT,
                          G_CALLBACK(onEntryTrans), (gpointer)view, G_CONNECT_SWAPPED);
  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_OPENGL_GROSS,
                          G_CALLBACK(onEntryGross), (gpointer)view, G_CONNECT_SWAPPED);
  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCE_OPENGL_PERSP,
                          G_CALLBACK(onEntryPersp), (gpointer)view, G_CONNECT_SWAPPED);
  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_PARAMETER_OPENGL_DETAILS,
                          G_CALLBACK(onEntryPrecision), (gpointer)view, G_CONNECT_SWAPPED);
}
/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_gl_view_dispose(GObject* obj)
{
  VisuGlView *view;

  g_debug("Visu GlView: dispose object %p.", (gpointer)obj);

  view = VISU_GL_VIEW(obj);
  if (view->priv->dispose_has_run)
    return;
  view->priv->dispose_has_run = TRUE;

  _setBox(VISU_BOXED(view), (VisuBox*)0);
  g_object_unref(view->priv->theta_anim);
  g_object_unref(view->priv->phi_anim);
  g_object_unref(view->priv->gross_anim);
  g_object_unref(view->priv->persp_anim);

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

  g_debug("Visu GlView: finalize object %p.", (gpointer)obj);
  g_hash_table_destroy(VISU_GL_VIEW(obj)->priv->animations);

  /* Chain up to the parent class */
  g_debug("Visu GlView: chain to parent.");
  G_OBJECT_CLASS(visu_gl_view_parent_class)->finalize(obj);
  g_debug("Visu GlView: freeing ... OK.");
}
static void visu_gl_view_get_property(GObject* obj, guint property_id,
                                      GValue *value, GParamSpec *pspec)
{
  VisuGlView *self = VISU_GL_VIEW(obj);

  g_debug("Visu GlView: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case THETA_PROP:
      if (visu_animation_isRunning(self->priv->theta_anim))
        visu_animation_getTo(self->priv->theta_anim, value);
      else
        g_value_set_double(value, self->camera.theta);
      g_debug("%g.", self->camera.theta);
      break;
    case PHI_PROP:
      if (visu_animation_isRunning(self->priv->phi_anim))
        visu_animation_getTo(self->priv->phi_anim, value);
      else
        g_value_set_double(value, self->camera.phi);
      g_debug("%g.", self->camera.phi);
      break;
    case OMEGA_PROP:
      g_value_set_double(value, self->camera.omega);
      g_debug("%g.", self->camera.omega);
      break;
    case XS_PROP:
      g_value_set_double(value, self->camera.xs);
      g_debug("%g.", self->camera.xs);
      break;
    case YS_PROP:
      g_value_set_double(value, self->camera.ys);
      g_debug("%g.", self->camera.ys);
      break;
    case GROSS_PROP:
      if (visu_animation_isRunning(self->priv->gross_anim))
        visu_animation_getTo(self->priv->gross_anim, value);
      else
        g_value_set_double(value, self->camera.gross);
      g_debug("%g.", self->camera.gross);
      break;
    case PERSP_PROP:
      if (visu_animation_isRunning(self->priv->persp_anim))
        visu_animation_getTo(self->priv->persp_anim, value);
      else
        g_value_set_double(value, self->camera.d_red);
      g_debug("%g.", self->camera.d_red);
      break;
    case ADJUST_PROP:
      g_value_set_boolean(value, self->priv->adjust);
      g_debug("%d.", self->priv->adjust);
      break;
    case PRECISION_PROP:
      g_debug("%g.", self->priv->precision);
      g_value_set_float(value, self->priv->precision);
      break;
    case BOX_PROP:
      g_debug("%p.", (gpointer)self->priv->box);
      g_value_set_object(value, self->priv->box);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_view_set_property(GObject* obj, guint property_id,
                                      const GValue *value, GParamSpec *pspec)
{
  VisuGlView *self = VISU_GL_VIEW(obj);

  g_debug("Visu GlView: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case THETA_PROP:
      g_debug("%g.", g_value_get_double(value));
      if (!visu_animatable_animateDouble(VISU_ANIMATABLE(self), self->priv->theta_anim,
                                         g_value_get_double(value),
                                         200, FALSE, VISU_ANIMATION_QUAD))
        visu_gl_view_setThetaPhiOmega(self, g_value_get_double(value),
                                      0., 0., TOOL_GL_CAMERA_THETA);
      break;
    case PHI_PROP:
      g_debug("%g.", g_value_get_double(value));
      if (!visu_animatable_animateDouble(VISU_ANIMATABLE(self), self->priv->phi_anim,
                                         g_value_get_double(value),
                                         200, FALSE, VISU_ANIMATION_QUAD))
        visu_gl_view_setThetaPhiOmega(self, 0., g_value_get_double(value),
                                      0., TOOL_GL_CAMERA_PHI);
      break;
    case OMEGA_PROP:
      visu_gl_view_setThetaPhiOmega(self, 0., 0., g_value_get_double(value),
                                    TOOL_GL_CAMERA_OMEGA);
      g_debug("%g.", self->camera.omega);
      break;
    case XS_PROP:
      visu_gl_view_setXsYs(self, g_value_get_double(value), 0.,
                           TOOL_GL_CAMERA_XS);
      g_debug("%g.", self->camera.xs);
      break;
    case YS_PROP:
      visu_gl_view_setXsYs(self, 0., g_value_get_double(value),
                           TOOL_GL_CAMERA_YS);
      g_debug("%g.", self->camera.ys);
      break;
    case GROSS_PROP:
      g_debug("%g.", g_value_get_double(value));
      if (!visu_animatable_animateDouble(VISU_ANIMATABLE(self), self->priv->gross_anim,
                                         g_value_get_double(value),
                                         200, FALSE, VISU_ANIMATION_QUAD))
        visu_gl_view_setGross(self, g_value_get_double(value));
      break;
    case PERSP_PROP:
      g_debug("%g.", g_value_get_double(value));
      if (!visu_animatable_animateDouble(VISU_ANIMATABLE(self), self->priv->persp_anim,
                                         g_value_get_double(value),
                                         200, FALSE, VISU_ANIMATION_QUAD))
        visu_gl_view_setPersp(self, g_value_get_double(value));
      break;
    case ADJUST_PROP:
      self->priv->adjust = g_value_get_boolean(value);
      autoAdjustDefault = g_value_get_boolean(value);
      g_debug("%d.", self->priv->adjust);
      break;
    case PRECISION_PROP:
      g_debug("%g.", g_value_get_float(value));
      visu_gl_view_setPrecision(self, g_value_get_float(value));
      break;
    case BOX_PROP:
      g_debug("%p.", g_value_get_object(value));
      _setBox(VISU_BOXED(obj), VISU_BOX(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_view_new:
 *
 * Create a new #VisuGlView object with default values.
 *
 * Returns: (transfer full): the newly created object.
 */
VisuGlView* visu_gl_view_new(void)
{
  return g_object_new(VISU_TYPE_GL_VIEW, NULL);
}

/**
 * visu_gl_view_new_full:
 * @w: the width ;
 * @h: the height.
 * @camera: a #ToolGlCamera object.
 *
 * Create a new #VisuGlView object with @camera values and the given
 * window size.
 *
 * Since: 3.9
 *
 * Returns: (transfer full): the newly created object.
 */
VisuGlView* visu_gl_view_new_full(guint w, guint h, const ToolGlCamera *camera)
{
  VisuGlView *view;

  view = VISU_GL_VIEW(g_object_new(VISU_TYPE_GL_VIEW, NULL));
  view->priv->width = w;
  view->priv->height = h;
  if (camera)
    {
      view->camera = *camera;
      tool_gl_matrix_modelize(&view->priv->M, &view->camera);
    }

  return view;
}

static gboolean _setBox(VisuBoxed *self, VisuBox *box)
{
  VisuGlView *view;
  float fact;

  g_return_val_if_fail(VISU_IS_GL_VIEW(self), FALSE);
  view = VISU_GL_VIEW(self);

  g_debug("OpenGL View: set box %p.", (gpointer)box);
  
  if (box == view->priv->box)
    return FALSE;

  if (view->priv->box)
    {
      g_signal_handler_disconnect(G_OBJECT(view->priv->box), view->priv->box_signal);
      g_signal_handler_disconnect(G_OBJECT(view->priv->box), view->priv->unit_signal);
      g_signal_handler_disconnect(G_OBJECT(view->priv->box), view->priv->bc_signal);
      g_object_unref(view->priv->box);
    }
  if (box)
    {
      g_object_ref(box);
      view->priv->box_signal =
        g_signal_connect(G_OBJECT(box), "SizeChanged",
                         G_CALLBACK(onSizeChanged), (gpointer)view);
      view->priv->unit_signal =
	g_signal_connect_swapped(G_OBJECT(box), "unit-changed",
                                 G_CALLBACK(onUnitChanged), (gpointer)view);
      view->priv->bc_signal =
	g_signal_connect(G_OBJECT(box), "notify::boundary",
			 G_CALLBACK(onBoundaryChanged), (gpointer)view);

      /* Update lengths. */
      if (view->priv->adjust || view->camera.length0 <= 0.f)
        visu_gl_view_setRefLength(view,
                                  visu_box_getGlobalSize(box, FALSE),
                                  visu_box_getUnit(box));
      else
        {
          fact = 1.;
          if (view->camera.unit != TOOL_UNITS_UNDEFINED &&
              visu_box_getUnit(box) != TOOL_UNITS_UNDEFINED)
            fact = tool_physic_getUnitValueInMeter(view->camera.unit) /
              tool_physic_getUnitValueInMeter(visu_box_getUnit(box));
          g_debug("Visu GlView: ref length factor %g (%d %d).",
                      fact, view->camera.unit, visu_box_getUnit(box));
          visu_gl_view_setRefLength(view,
                                    view->camera.length0 * fact,
                                    visu_box_getUnit(box));
        }
      visu_gl_view_setObjectRadius(view,
                                   visu_box_getGlobalSize(box, TRUE),
                                   visu_box_getUnit(box));
      /* Update camera. */
      _setBoundary(view, box);
    }
  else
    {
      view->priv->box_signal = 0;
      view->priv->unit_signal = 0;
      view->priv->bc_signal = 0;
    }
  view->priv->box = box;

  return TRUE;
}
static void onSizeChanged(VisuBox *box, gfloat extens, gpointer data)
{
  VisuGlView *view = VISU_GL_VIEW(data);

  g_debug("Visu GlView: caught 'SizeChanged'.");
  visu_gl_view_setObjectRadius(view, extens, visu_box_getUnit(box));
  g_debug("Visu GlView: done 'SizeChanged'.");
}
static void onUnitChanged(VisuGlView *view, gfloat fact)
{
  ToolUnits unit;

  g_debug("Visu GlView: caught 'unit-changed'.");
  unit = visu_box_getUnit(view->priv->box);
  if (view->camera.unit != TOOL_UNITS_UNDEFINED && unit != TOOL_UNITS_UNDEFINED)
    fact = tool_physic_getUnitValueInMeter(view->camera.unit) /
      tool_physic_getUnitValueInMeter(unit);
  visu_gl_view_setRefLength(view, view->camera.length0 * fact, unit);
  /* Box size changed will be emitted after, so the object radius will
     be updated automatically, except for undefined units. */
  if (view->camera.unit == TOOL_UNITS_UNDEFINED || unit == TOOL_UNITS_UNDEFINED)
    tool_gl_camera_setExtens(&view->camera, view->camera.extens, unit);

  g_signal_emit_by_name(view, "unit-changed", fact);
  g_debug("Visu GlView: done 'unit-changed'.");
}
static void onBoundaryChanged(VisuBox *box, GParamSpec *pspec _U_, gpointer data)
{
  _setBoundary(VISU_GL_VIEW(data), box);
}
static void _setBoundary(VisuGlView *view, VisuBox *box)
{
  switch (visu_box_getBoundary(box))
    {
    case (VISU_BOX_PERIODIC):
    case (VISU_BOX_FREE):
    case (VISU_BOX_WIRE_X):
    case (VISU_BOX_WIRE_Y):
    case (VISU_BOX_WIRE_Z):
    case (VISU_BOX_SURFACE_XY):
      tool_gl_camera_setUpAxis(&view->camera, TOOL_XYZ_Z);
      break;
    case (VISU_BOX_SURFACE_YZ):
      tool_gl_camera_setUpAxis(&view->camera, TOOL_XYZ_X);
      break;
    case (VISU_BOX_SURFACE_ZX):
      tool_gl_camera_setUpAxis(&view->camera, TOOL_XYZ_Y);
      break;
    }
  tool_gl_matrix_modelize(&view->priv->M, &view->camera);
  g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
}
static VisuBox* _getBox(VisuBoxed *boxed)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(boxed), (VisuBox*)0);

  return VISU_GL_VIEW(boxed)->priv->box;
}
static VisuAnimation* _getAnimation(const VisuAnimatable *animatable,
                                    const gchar *prop)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(animatable), (VisuAnimation*)0);

  return g_hash_table_lookup(VISU_GL_VIEW(animatable)->priv->animations, prop);
}

/**
 * visu_gl_view_getDetailLevel:
 * @view: a valid #VisuGlView object ;
 * @dimension: the size of the object which asks for its number of facettes.
 *
 * This is a function to get the number of "facettes" advised
 * by the server (according to its policy on rendering)
 * to draw an object according to a given dimension.
 *
 * Returns: the number of facettes the object should used.
 */
gint visu_gl_view_getDetailLevel(const VisuGlView *view, float dimension)
{
  gint rsize;     
  gint nlat;
#define NLAT_MIN 35
#define NLAT_MAX 75
#define RSIZE_MIN  10
#define RSIZE_MAX 250

#define NLAT_V_MIN 0
#define NLAT_V_MAX (NLAT_MIN)
#define RSIZE_V_MIN  0
#define RSIZE_V_MAX (RSIZE_MIN)

#define NLAT_MINI 4
#define NLAT_MAXI 100

  static float fac = -1.0f, fac_v = -1.0f;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), 0);

  /* g_debug("Visu GlView: get GL details for window %dx%d.", */
  /*         view->priv->width, view->priv->height); */
  /* g_debug(" | gross = %g.", view->camera.gross); */
  /* g_debug(" | persp = %g.", view->camera.d_red); */
  /* g_debug(" | lgth0 = %g.", view->camera.length0); */

  /* calculate once fac and fac_v!... */
  if(fac < 0.0f) {
    fac = ((float)(NLAT_MAX - NLAT_MIN))/(RSIZE_MAX - RSIZE_MIN);
    fac_v = ((float)(NLAT_V_MAX - NLAT_V_MIN))/(RSIZE_V_MAX - RSIZE_V_MIN);
  }

  rsize = (int)((float)MIN(view->priv->width, view->priv->height) *
		(0.5 * dimension / view->camera.length0 * view->camera.gross *
		 view->camera.d_red / (view->camera.d_red - 1.)));
  
  if(rsize < RSIZE_MIN) {
    nlat = (int)(NLAT_V_MIN + fac_v * (rsize - RSIZE_V_MIN));
    if(nlat < NLAT_MINI) nlat = NLAT_MINI;
  }
  else if(rsize > RSIZE_MAX) {
    nlat = NLAT_MAX;
  }
  else {
    nlat = (int)(NLAT_MIN + fac * (rsize - RSIZE_MIN));
  }

  nlat = (int)((float)nlat * view->priv->precision);
  nlat = CLAMP(nlat, NLAT_MINI, NLAT_MAXI);
   
  /* g_debug(" | nlat  = %d.", nlat); */
  return nlat;
}
/**
 * visu_gl_view_setPrecision:
 * @view: a #VisuGlView object.
 * @value: a positive value (1. is normal precision).
 *
 * This function change the value of the parameter precisionOfRendering. It
 * changes the number of facettes advised for every objects. It allows to
 * increase or decrease the number of polygons drawn and thus acts on the
 * speed of rendering.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setPrecision(VisuGlView *view, float value)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);

  if (value <= 0. || value == view->priv->precision)
    return FALSE;
  
  view->priv->precision = value;
  _defaultDetails = value * 100.f;
  g_object_notify_by_pspec(G_OBJECT(view), properties[PRECISION_PROP]);
  g_signal_emit(G_OBJECT(view), _signals[FACETTES_CHANGED_SIGNAL], 0);

  return TRUE;
}
/**
 * visu_gl_view_getPrecision:
 * @view: a #VisuGlView object.
 *
 * This function retrieve the value of the parameter precisionOfRendering.
 *
 * Returns: the actual precision.
 */
float visu_gl_view_getPrecision(const VisuGlView *view)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), 1.f);

  return view->priv->precision;
}
/**
 * visu_gl_view_getFileUnitPerPixel:
 * @view: a valid #VisuGlView object.
 *
 * This method is used to know the ratio of a pixel with the unit of the file.
 * WARNING : this method is valid only when the camera is positioned at infinity.
 * 
 * Returns: how much of a unit of file is in a pixel.
 */
float visu_gl_view_getFileUnitPerPixel(const VisuGlView *view)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), 0.);

  return 2.f * (MAX(view->camera.d_red, 100.f) * view->camera.length0 - view->camera.extens) / view->camera.gross / MAX(view->camera.d_red, 100.f) / (float)MIN(view->priv->width, view->priv->height);
}
/**
 * visu_gl_view_getZCoordinate:
 * @view: a #VisuGlView object.
 * @xyz: a cartesian point.
 *
 * Use this routine to know the Z value of a real point defined by
 * @xyz in caretsian coordinates.
 */
float visu_gl_view_getZCoordinate(VisuGlView *view, const float xyz[3])
{
  gfloat w1[4], w2[4], winZ;
  ToolGlMatrix MV;
  const gfloat v[4] = {xyz[0], xyz[1], xyz[2], 1.f};

  g_return_val_if_fail(view, 0.5f);

  g_debug("OpenGL View: get z coordinates from %gx%gx%g.",
          xyz[0], xyz[1], xyz[2]);
  visu_gl_view_getModelView(view, &MV);
  tool_gl_matrix_vectorProd(w1, &MV, v);
  tool_gl_matrix_vectorProd(w2, &view->priv->P, w1);
  winZ = (w2[2]/w2[3] + 1.f) * 0.5f;
  g_debug(" | winZ coord: %g", winZ);
  return winZ;
}
/**
 * visu_gl_view_getRealCoordinates:
 * @view: a #VisuGlView object.
 * @xyz: a location to store the result.
 * @winx: position on X axis of screen.
 * @winy: position on Y axis of screen.
 * @winz: height before projection on screen.
 *
 * Use this routine to get the cartesian coordinates in real space of
 * a point located at @winx and @winy on screen.
 */
void visu_gl_view_getRealCoordinates(VisuGlView *view, float xyz[3],
				   float winx, float winy, float winz)
{
  ToolGlMatrix MV, PMV, invPMV;
  gfloat u[4], v[4];

  g_return_if_fail(VISU_IS_GL_VIEW(view));

  visu_gl_view_getModelView(view, &MV);
  tool_gl_matrix_matrixProd(&PMV, &view->priv->P, &MV);
  tool_gl_matrix_inv(&invPMV, &PMV);

  v[0] = 2.f * winx / (gfloat)view->priv->width - 1.f;
  v[1] = 1.f - 2.f * winy / (gfloat)view->priv->height;
  v[2] = 2.f * winz - 1.f;
  v[3] = 1.f;

  tool_gl_matrix_vectorProd(u, &invPMV, v);

  xyz[0] = u[0] / u[3];
  xyz[1] = u[1] / u[3];
  xyz[2] = u[2] / u[3];
  g_debug("OpenGL View: get real coordinates from %gx%gx%g: %gx%gx%g.",
          winx, winy, winz, xyz[0], xyz[1], xyz[2]);
}
/**
 * visu_gl_view_getWinCoordinates:
 * @view: a #VisuGlView object.
 * @win: a location to store the result.
 * @xyz: (array fixed-size=3): cartesian coordiantes.
 *
 * Use this routine to get the window coordinates of a point from real
 * space.
 *
 * Since: 3.9
 */
void visu_gl_view_getWinCoordinates(const VisuGlView *view, gfloat win[3],
                                    const gfloat xyz[3])
{
  ToolGlMatrix MVP;
  gfloat u[4], v[4];

  g_return_if_fail(VISU_IS_GL_VIEW(view));

  u[0] = xyz[0];
  u[1] = xyz[1];
  u[2] = xyz[2];
  u[3] = 1.f;
  visu_gl_view_getModelViewProjection(view, &MVP);
  tool_gl_matrix_vectorProd(v, &MVP, u);

  win[0] = v[0] / v[3];
  win[1] = v[1] / v[3];
  win[2] = v[2] / v[3];

  win[0] = (win[0] + 1.f) / 2.f * (gfloat)view->priv->width;
  win[1] = (1.f - win[1]) / 2.f * (gfloat)view->priv->height;
  win[2] = (win[2] + 1.f) / 2.f;

  g_debug("OpenGL View: get win coordinates from %gx%gx%g: %gx%gx%g.",
          xyz[0], xyz[1], xyz[2], win[0], win[1], win[2]);
}

/**
 * visu_gl_view_alignToAxis:
 * @view: a #VisuGlView object.
 * @axis: an axis.
 *
 * Rotate the view to align it with the given box axis.
 *
 * Since: 3.8
 **/
void visu_gl_view_alignToAxis(VisuGlView *view, ToolXyzDir axis)
{
  float red[3], xyz[3], xyz_[3], sph[3];
  int permut[3][3] = {{1,2,0}, {2,0,1}, {0,1,2}};

  g_return_if_fail(VISU_IS_GL_VIEW(view));

  if (!view->priv->box)
    return;

  red[0] = (axis == TOOL_XYZ_X) ? 1.f : 0.f;
  red[1] = (axis == TOOL_XYZ_Y) ? 1.f : 0.f;
  red[2] = (axis == TOOL_XYZ_Z) ? 1.f : 0.f;
  visu_box_convertBoxCoordinatestoXYZ(view->priv->box, xyz, red);
  xyz_[0] = xyz[permut[view->camera.upAxis][0]];
  xyz_[1] = xyz[permut[view->camera.upAxis][1]];
  xyz_[2] = xyz[permut[view->camera.upAxis][2]];
  tool_matrix_cartesianToSpherical(sph, xyz_);

  g_object_set(view, "theta", sph[TOOL_MATRIX_SPHERICAL_THETA],
               "phi", sph[TOOL_MATRIX_SPHERICAL_PHI], NULL);
}

/**
 * visu_gl_view_setThetaPhiOmega:
 * @view: a #VisuGlView object ;
 * @valueTheta: a floatinf point value in degrees ;
 * @valuePhi: a floating point value in degrees ;
 * @valueOmega: a floating point value in degrees ;
 * @mask: to specified what values will be changed.
 *
 * This method is used to change the camera orientation for the given @view.
 * If necessary, this method will emit the 'ThetaPhiOmegaChanged' signal.
 *
 * Returns: TRUE if value is actually changed.
 */

gboolean visu_gl_view_setThetaPhiOmega(VisuGlView *view, float valueTheta,
                                       float valuePhi, float valueOmega, int mask)
{
  int res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);

  g_debug("OpenGL View: changing theta / phi / omega (%d).", mask);
  g_debug("OpenGL View: to %g / %g / %g.",
              valueTheta, valuePhi, valueOmega);
  res = tool_gl_camera_setThetaPhiOmega(&view->camera,
                                        valueTheta, valuePhi, valueOmega, mask);
  g_debug("OpenGL View: to %g / %g / %g.",
              view->camera.theta, view->camera.phi, view->camera.omega);
  if (res & TOOL_GL_CAMERA_THETA)
    g_object_notify_by_pspec(G_OBJECT(view), properties[THETA_PROP]);
  if (res & TOOL_GL_CAMERA_PHI)
    g_object_notify_by_pspec(G_OBJECT(view), properties[PHI_PROP]);
  if (res & TOOL_GL_CAMERA_OMEGA)
    g_object_notify_by_pspec(G_OBJECT(view), properties[OMEGA_PROP]);
  if (res)
    {
      anglesDefault[0] = view->camera.theta;
      anglesDefault[1] = view->camera.phi;
      anglesDefault[2] = view->camera.omega;
      tool_gl_matrix_modelize(&view->priv->M, &view->camera);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
    
  return (res > 0);
}
/**
 * visu_gl_view_setXsYs:
 * @view: a #VisuGlView object ;
 * @valueX: a floatinf point value in the bounding box scale
 *          (1 is the size of the bounding box) ;
 * @valueY: a floating point value in bounding box scale ;
 * @mask: to specified what values will be changed.
 *
 * This method is used to change the camera position for the given @view.
 * If necessary, this method will emit the 'XsYsChanged' signal.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setXsYs(VisuGlView *view,
                              float valueX, float valueY, int mask)
{
  int res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);
  
  res = tool_gl_camera_setXsYs(&view->camera, valueX, valueY, mask);
  if (res & TOOL_GL_CAMERA_XS)
    g_object_notify_by_pspec(G_OBJECT(view), properties[XS_PROP]);
  if (res & TOOL_GL_CAMERA_YS)
    g_object_notify_by_pspec(G_OBJECT(view), properties[YS_PROP]);
  if (res)
    {
      translatDefault[0] = view->camera.xs;
      translatDefault[1] = view->camera.ys;
      tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
    
  return (res > 0);
}

/**
 * visu_gl_view_setGross:
 * @view: a #VisuGlView object ;
 * @value: a positive floating point value.
 *
 * This method is used to change the camera zoom for the given @view.
 * If necessary, this method will emit the 'GrossChanged' signal and
 * the 'FacetteChangedChanged' signal.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setGross(VisuGlView *view, float value)
{
  gboolean res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);

  res = tool_gl_camera_setGross(&view->camera, value);
  if (res)
    {
      grossDefault = view->camera.gross;

      g_object_notify_by_pspec(G_OBJECT(view), properties[GROSS_PROP]);
      if (!visu_animation_isRunning(view->priv->gross_anim))
        g_signal_emit(view, _signals[FACETTES_CHANGED_SIGNAL],
                      0, NULL);

      tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
  return res;
}

/**
 * visu_gl_view_setPersp:
 * @view: a #VisuGlView object ;
 * @value: a positive floating point value (> 1.1).
 *
 * This method is used to change the camera perspective for the given @view.
 * If necessary, this method will emit the 'PerspChanged' signal and
 * the 'FacetteChangedChanged' signal.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setPersp(VisuGlView *view, float value)
{
  gboolean res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);
  
  res = tool_gl_camera_setPersp(&view->camera, value);
  if (res)
    {
      perspDefault = view->camera.d_red;
      
      g_debug("Visu GlView: emitting signals on persp changed.");
      g_object_notify_by_pspec(G_OBJECT(view), properties[PERSP_PROP]);
      if (!visu_animation_isRunning(view->priv->persp_anim))
        g_signal_emit(view, _signals[FACETTES_CHANGED_SIGNAL],
                      0, NULL);

      tool_gl_matrix_modelize(&view->priv->M, &view->camera);
      tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
    
  return res;
}

/**
 * visu_gl_view_setViewport:
 * @view: a #VisuGlView object ;
 * @width: the new horizontal size ;
 * @height: the new vertical size.
 *
 * It changes the size of the OpenGl area and reccompute the OpenGL viewport.
 * Warning : it doesn't change the size of the window.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setViewport(VisuGlView *view, guint width, guint height)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);
  
  g_debug("Visu GlView: set viewport size (%dx%d) for window %p.",
          width, height, (gpointer)view);
  
  if (view->priv->width == width && view->priv->height == height)
    return FALSE;

  g_debug(" | old values were %dx%d.", view->priv->width,
	      view->priv->height);
  view->priv->width = width;
  view->priv->height = height;

  g_signal_emit(view, _signals[WIDTH_HEIGHT_CHANGED_SIGNAL], 0, NULL);
  g_signal_emit(view, _signals[FACETTES_CHANGED_SIGNAL], 0, NULL);

  tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
  g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    
  return TRUE;
}
/**
 * visu_gl_view_getWidth:
 * @view: a #VisuGlView object
 *
 * Retrieve the viewport width.
 *
 * Since: 3.9
 *
 * Returns: the viewport width.
 **/
guint visu_gl_view_getWidth(const VisuGlView *view)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), 0);

  return view->priv->width;
}
/**
 * visu_gl_view_getHeight:
 * @view: a #VisuGlView object
 *
 * Retrieve the viewport height.
 *
 * Since: 3.9
 *
 * Returns: the viewport height.
 **/
guint visu_gl_view_getHeight(const VisuGlView *view)
{
  g_return_val_if_fail(VISU_IS_GL_VIEW(view), 0);

  return view->priv->height;
}

/**
 * visu_gl_view_setRefLength:
 * @view: a #VisuGlView object ;
 * @lg: the new value.
 * @units: the unit to read @lg with.
 *
 * This method is used to change the camera reference length for the given @view.
 * If necessary, this method will modelize and emit the 'RefLengthChanged' signal.
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setRefLength(VisuGlView *view, float lg, ToolUnits units)
{
  gboolean res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);
  
  res = tool_gl_camera_setRefLength(&view->camera, lg, units);
  if (res)
    {
      g_signal_emit(view, _signals[REF_LENGTH_CHANGED_SIGNAL],
                    0, NULL);
      g_signal_emit(view, _signals[FACETTES_CHANGED_SIGNAL],
		    0, NULL);
      tool_gl_matrix_modelize(&view->priv->M, &view->camera);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
    
  return res;
}
/**
 * visu_gl_view_setObjectRadius:
 * @view: a #VisuGlView object ;
 * @lg: the new value.
 * @units: the unit to read @lg with.
 *
 * This method is used to change the window frustum for the given @view.
 * If necessary, this method will project and emit the
 * 'NearFarChanged' signal.
 *
 * Since: 3.7
 *
 * Returns: TRUE if value is actually changed.
 */
gboolean visu_gl_view_setObjectRadius(VisuGlView *view, float lg, ToolUnits units)
{
  gboolean res;

  g_return_val_if_fail(VISU_IS_GL_VIEW(view), FALSE);
  
  res = tool_gl_camera_setExtens(&view->camera, lg, units);
  if (res)
    {
      tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
      g_signal_emit(view, _signals[CHANGED_SIGNAL], 0);
    }
    
  return res;
}

/**
 * visu_gl_view_rotateBox:
 * @view: a valid #VisuGlView object ;
 * @dTheta: a float value ;
 * @dPhi: a float value ;
 * @angles: (out) (array fixed-size=2): a storing area two floats.
 *
 * This methods rotates the camera of the given @view of (@dTheta, @dPhi) and
 * put new theta and phi angles in @angles, first being theta and second phi.
 */
void visu_gl_view_rotateBox(VisuGlView *view, float dTheta, float dPhi, float angles[2])
{
  GValue val = G_VALUE_INIT;
  
  g_return_if_fail(view && angles);

  g_value_init(&val, G_TYPE_DOUBLE);
  if (visu_animation_isRunning(view->priv->theta_anim))
    {
      visu_animation_getTo(view->priv->theta_anim, &val);
      angles[0] = g_value_get_double(&val) + dTheta;
    }
  else
    angles[0] = view->camera.theta + dTheta;
  if (visu_animation_isRunning(view->priv->phi_anim))
    {
      visu_animation_getTo(view->priv->phi_anim, &val);
      angles[1] = g_value_get_double(&val) + dPhi;
    }
  else
    angles[1] = view->camera.phi + dPhi;
}
/**
 * visu_gl_view_rotateCamera:
 * @view: a valid #VisuGlView object ;
 * @dTheta: a float value ;
 * @dPhi: a float value ;
 * @angles: (out) (array fixed-size=3): a storing area three floats.
 *
 * This methods rotates the camera of the given @view of (@dTheta, @dPhi).
 * @dTheta is taken as displacement along camera x axis and dPhi along camera y axis.
 * Then, computations are done to obtain new theta, phi and omega values. They are
 * put in @angles, first being theta, second phi and third omega.
 */
void visu_gl_view_rotateCamera(VisuGlView *view, float dTheta, float dPhi, float angles[3])
{
  double cth, sth, cph, sph, com, som;
  double cdth, sdth, cdph, sdph;
  double Theta, Phi, Omega;
  #define RADTODEG 57.29577951
  float MinRprime[3], MinR[3];
  float Mspherical[3];
  float matPhi[3][3], matTheta[3][3], matOmega[3][3], matdPhi[3][3], matdTheta[3][3];
  float matPhiPrime[3][3], matThetaPrime[3][3];
  float matRprime2R[3][3];
  float matRes[3][3], matRes2[3][3];

  g_return_if_fail(view && angles);

  cth = cos(view->camera.theta * TOOL_PI180);
  sth = sin(view->camera.theta * TOOL_PI180);
  cph = cos(view->camera.phi * TOOL_PI180);
  sph = sin(view->camera.phi * TOOL_PI180);
  com = cos(view->camera.omega * TOOL_PI180);
  som = sin(view->camera.omega * TOOL_PI180);

  cdth = cos(dTheta * TOOL_PI180);
  sdth = sin(dTheta * TOOL_PI180);
  cdph = cos(dPhi * TOOL_PI180);
  sdph = sin(dPhi * TOOL_PI180);


  matPhi[0][0] = cph;
  matPhi[1][0] = sph;
  matPhi[2][0] = 0.;
  matPhi[0][1] = -sph;
  matPhi[1][1] = cph;
  matPhi[2][1] = 0.;
  matPhi[0][2] = 0.;
  matPhi[1][2] = 0.;
  matPhi[2][2] = 1.;

  matTheta[0][0] = cth;
  matTheta[1][0] = 0.;
  matTheta[2][0] = -sth;
  matTheta[0][1] = 0.;
  matTheta[1][1] = 1.;
  matTheta[2][1] = 0.;
  matTheta[0][2] = sth;
  matTheta[1][2] = 0.;
  matTheta[2][2] = cth;

  matOmega[0][0] = com;
  matOmega[1][0] = som;
  matOmega[2][0] = 0.;
  matOmega[0][1] = -som;
  matOmega[1][1] = com;
  matOmega[2][1] = 0.;
  matOmega[0][2] = 0.;
  matOmega[1][2] = 0.;
  matOmega[2][2] = 1.;

  matdPhi[0][0] = 1.;
  matdPhi[1][0] = 0.;
  matdPhi[2][0] = 0.;
  matdPhi[0][1] = 0.;
  matdPhi[1][1] = cdph;
  matdPhi[2][1] = -sdph;
  matdPhi[0][2] = 0.;
  matdPhi[1][2] = sdph;
  matdPhi[2][2] = cdph;

  matdTheta[0][0] = cdth;
  matdTheta[1][0] = 0.;
  matdTheta[2][0] = -sdth;
  matdTheta[0][1] = 0.;
  matdTheta[1][1] = 1.;
  matdTheta[2][1] = 0.;
  matdTheta[0][2] = sdth;
  matdTheta[1][2] = 0.;
  matdTheta[2][2] = cdth;

  tool_matrix_productMatrix(matRes, matdPhi, matdTheta);
  tool_matrix_productMatrix(matRes2, matOmega, matRes);
  tool_matrix_productMatrix(matRes, matTheta, matRes2);
  tool_matrix_productMatrix(matRprime2R, matPhi, matRes);

  MinRprime[0] = 0.;
  MinRprime[1] = 0.;
  MinRprime[2] = 1.;
  tool_matrix_productVector(MinR, matRprime2R, MinRprime);
/*   g_debug("M : %f %f %f -> %f", MinR[0], MinR[1], MinR[2], */
/* 	  MinR[0]*MinR[0] + MinR[1]*MinR[1] + MinR[2]*MinR[2]); */

/*   cartesian_to_spherical(Mspherical, MinR); */
  Mspherical[0] = sqrt(MinR[0]*MinR[0] + MinR[1]*MinR[1] + MinR[2]*MinR[2]);
  if (MinR[1] == 0 && MinR[0] == 0)
    {
      Mspherical[1] = (MinR[2] > 0.)?0.:180.;
      Mspherical[2] = view->camera.phi;
    }
  else
    {
      Mspherical[1] = acos(MinR[2] / Mspherical[0]) * RADTODEG;
      if (MinR[0] == 0.)
	Mspherical[2] = (MinR[1] > 0.)?90.:-90.;
      else
	{
	  Mspherical[2] = atan(MinR[1] / MinR[0]) * RADTODEG;
	  if (MinR[0] < 0.)
	    Mspherical[2] += 180.;
	}
    }
/*   g_debug("avant %f %f", Mspherical[1], Mspherical[2]); */
  while (Mspherical[1] - view->camera.theta < -90.)
    Mspherical[1] += 360.;
  while (Mspherical[1] - view->camera.theta > 90.)
    Mspherical[1] -= 360.;
  while (Mspherical[2] - view->camera.phi < -90.)
    Mspherical[2] += 360.;
  while (Mspherical[2] - view->camera.phi > 90.)
    Mspherical[2] -= 360.;
/*   g_debug("après %f %f", Mspherical[1], Mspherical[2]); */

  Theta = Mspherical[1];
  Phi = Mspherical[2];

/*   g_debug("%f %f, %f %f", view->camera.theta, view->camera.phi, Theta, Phi); */
/*   g_debug("%f %f, %f %f", dTheta, dPhi, Theta - view->camera.theta, Phi -  view->camera.phi); */

  cth = cos(Theta * TOOL_PI180);
  sth = sin(Theta * TOOL_PI180);
  cph = cos(Phi * TOOL_PI180);
  sph = sin(Phi * TOOL_PI180);

  matPhiPrime[0][0] = cph;
  matPhiPrime[1][0] = -sph;
  matPhiPrime[2][0] = 0.;
  matPhiPrime[0][1] = sph;
  matPhiPrime[1][1] = cph;
  matPhiPrime[2][1] = 0.;
  matPhiPrime[0][2] = 0.;
  matPhiPrime[1][2] = 0.;
  matPhiPrime[2][2] = 1.;

  matThetaPrime[0][0] = cth;
  matThetaPrime[1][0] = 0.;
  matThetaPrime[2][0] = sth;
  matThetaPrime[0][1] = 0.;
  matThetaPrime[1][1] = 1.;
  matThetaPrime[2][1] = 0.;
  matThetaPrime[0][2] = -sth;
  matThetaPrime[1][2] = 0.;
  matThetaPrime[2][2] = cth;

  tool_matrix_productMatrix(matRes2, matPhiPrime, matRprime2R);
  tool_matrix_productMatrix(matRes, matThetaPrime, matRes2);

  MinRprime[0] = 0.;
  MinRprime[1] = 1.;
  MinRprime[2] = 0.;
  tool_matrix_productVector(MinR, matRes, MinRprime);
/*   g_debug("vect u : %f %f %f -> %f", MinR[0], MinR[1], MinR[2], */
/* 	  MinR[0]*MinR[0] + MinR[1]*MinR[1] + MinR[2]*MinR[2]); */
  Omega = acos(CLAMP(MinR[1], -1.f, 1.f)) * RADTODEG;
  if (MinR[0] > 0.)
    Omega = -Omega;
  while (Omega - view->camera.omega < -90.)
    Omega += 360.;
  while (Omega - view->camera.omega > 90.)
    Omega -= 360.;

  /*   g_debug("Theta phi omega : %f %f %f", Theta, Phi, Omega); */
  angles[0] = Theta;
  angles[1] = Phi;
  angles[2] = Omega;
}

/**
 * visu_gl_view_getProjection:
 * @view: a #VisuGlView object
 * @P: location for a #ToolGlMatrix
 *
 * Stores the projection matrix in @P.
 *
 * Since: 3.9
 **/
void visu_gl_view_getProjection(const VisuGlView *view, ToolGlMatrix *P)
{
  g_return_if_fail(VISU_IS_GL_VIEW(view));

  if (!view->priv->P.c[0][0])
    tool_gl_matrix_project(&view->priv->P, &view->camera, view->priv->width, view->priv->height);
  if (P)
    *P = view->priv->P;
}

/**
 * visu_gl_view_getModelView:
 * @view: a #VisuGlView object
 * @MV: location ofr a #ToolGlMatrix
 *
 * Stores the modelview matrix in @MV.
 *
 * Since: 3.9
 **/
void visu_gl_view_getModelView(const VisuGlView *view, ToolGlMatrix *MV)
{
  g_return_if_fail(VISU_IS_GL_VIEW(view));

  if (view->priv->box)
    {
      ToolGlMatrix V;
      float centre[3];
      tool_gl_matrix_setIdentity(&V);
      visu_box_getCentre(view->priv->box, centre);
      V.c[3][0] = -centre[0];
      V.c[3][1] = -centre[1];
      V.c[3][2] = -centre[2];
      if (MV)
        tool_gl_matrix_matrixProd(MV, &view->priv->M, &V);
    }
  else if (MV)
    *MV = view->priv->M;
}

/**
 * visu_gl_view_getModelViewProjection:
 * @view: a #VisuGlView object
 * @MVP: location ofr a #ToolGlMatrix
 *
 * Stores the modelview matrix in product with the projection matrix in @MVP.
 *
 * Since: 3.9
 **/
void visu_gl_view_getModelViewProjection(const VisuGlView *view, ToolGlMatrix *MVP)
{
  ToolGlMatrix MV, P;

  g_return_if_fail(VISU_IS_GL_VIEW(view));

  visu_gl_view_getModelView(view, &MV);
  visu_gl_view_getProjection(view, &P);
  tool_gl_matrix_matrixProd(MVP, &P, &MV);
}

/***************************/
/* Dealing with resources. */
/***************************/
static void exportParametersVisuGlView(GString *data, VisuData *dataObj _U_)
{
  g_string_append_printf(data, "# %s\n", DESC_PARAMETER_OPENGL_DETAILS);
  g_string_append_printf(data, "%s: %d\n\n", FLAG_PARAMETER_OPENGL_DETAILS,
                         (int)(_defaultDetails));

  g_string_append_printf(data, "# %s\n", DESC_PARAMETER_AUTO_ADJUST);
  g_string_append_printf(data, "%s: %d\n\n", FLAG_PARAMETER_AUTO_ADJUST, autoAdjustDefault);
}
static void onEntryPrecision(VisuGlView *view, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_view_setPrecision(view, _defaultDetails / 100.f);
}
static void onEntryAngles(VisuGlView *view, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_view_setThetaPhiOmega
    (view, anglesDefault[0], anglesDefault[1], anglesDefault[2],
     TOOL_GL_CAMERA_THETA | TOOL_GL_CAMERA_PHI | TOOL_GL_CAMERA_OMEGA);
}
static void onEntryTrans(VisuGlView *view, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_view_setXsYs(view, translatDefault[0], translatDefault[1],
                       TOOL_GL_CAMERA_XS | TOOL_GL_CAMERA_YS);
}
static void onEntryGross(VisuGlView *view, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_view_setGross(view, grossDefault);
}
static void onEntryPersp(VisuGlView *view, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_view_setPersp(view, perspDefault);
}
static void exportResourcesVisuGlView(GString *data, VisuData *dataObj _U_)
{
  visu_config_file_exportComment(data, DESC_RESOURCE_OPENGL_ANGLES);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_OPENGL_ANGLES, NULL,
                               "%9.3f %9.3f %9.3f",
                               anglesDefault[0], anglesDefault[1], anglesDefault[2]);

  visu_config_file_exportComment(data, DESC_RESOURCE_OPENGL_TRANSLAT);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_OPENGL_TRANSLAT, NULL,
                               "%9.3f %9.3f",
                               translatDefault[0], translatDefault[1]);

  visu_config_file_exportComment(data, DESC_RESOURCE_OPENGL_GROSS);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_OPENGL_GROSS, NULL,
                               "%9.3f", grossDefault);

  visu_config_file_exportComment(data, DESC_RESOURCE_OPENGL_PERSP);
  visu_config_file_exportEntry(data, FLAG_RESOURCE_OPENGL_PERSP, NULL,
                               "%9.3f", perspDefault);

  visu_config_file_exportComment(data, "");
}

