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

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

#include <stdlib.h>
#include <math.h>
#include <string.h>

#include <iface_animatable.h>
#include <coreTools/toolMatrix.h>

/**
 * SECTION:plane
 * @short_description: Adds capabilities to draw and handle planes.
 *
 * <para>A #VisuPlane is a GObject. It is defined by its normal vector and
 * the distance of the plane with the origin (see
 * visu_plane_setNormalVector() and visu_plane_setDistanceFromOrigin()). When
 * these informations are given and an #VisuGlView is used to render
 * the plane, V_Sim computes the intersections of the plane with the
 * bounding box (see visu_plane_getIntersection()).</para>
 * <para>#VisuPlane can be used to hide nodes defining their
 * visu_plane_setHiddenState() and visu_plane_set_setHiddingMode(). A list of planes
 * can also be exported or imported from an XML file using
 * visu_plane_set_exportXMLFile() and visu_plane_set_parseXMLFile().</para>
 * <para>#VisuPlane can have transparency but the support of it is limited
 * to one plane. If several planes are drawn with transparency, they
 * may hide each other because of the implementation of transparency
 * in OpenGL (planes are treated as single polygons).</para>
 */

struct _VisuPlane
{
  /* Internal object gestion. */
  VisuObject parent;
  gboolean dispose_has_run;

  /* Normal vector, unitary. */
  float nVect[3];
  /* Normal vector, user given. */
  float nVectUser[3];

  /* Distance between origin and intersection of the plane
     and the line made by origin and normal vector. */
  float dist;

  /* ToolColor of that plane. */
  ToolColor *color;
  gfloat opacity;
  VisuAnimation *opacity_anim;

  /* Internal variables */
  VisuBox *box;
  gulong size_signal;

  /* Intersections with the bounding box.
     Consist of a GList of float[3], can be NULL if
     there is no interstection. */
  GList *inter;
  /* Isobarycenter G of all intersection points, required to order
     these points to form a convex polygon. */
  float pointG[3];
  /* The plane can hide the nodes on one of its side.
     This variable can be VISU_PLANE_SIDE_PLUS or VISU_PLANE_SIDE_MINUS or
     VISU_PLANE_SIDE_NONE. It codes the side of the plane which hides the nodes.
     If VISU_PLANE_SIDE_NONE is selected all nodes are rendered. */
  int hiddenSide;
};

enum
  {
    VISU_PLANE_MOVED_SIGNAL,
    VISU_PLANE_RENDERING_SIGNAL,
    VISU_PLANE_NB_SIGNAL
  };

enum
  {
    PROP_0,
    DIST_PROP,
    VECT_PROP,
    COLOR_PROP,
    HIDDING_PROP,
    RENDERED_PROP,
    OPACITY_PROP,
    N_PROP,
    ADJUST_PROP,
    BOX_PROP
  };
static GParamSpec *properties[N_PROP];

/* Internal variables. */
static guint plane_signals[VISU_PLANE_NB_SIGNAL] = { 0 };

/* Object gestion methods. */
static void visu_plane_dispose   (GObject* obj);
static void visu_plane_finalize  (GObject* obj);
static void visu_boxed_interface_init(VisuBoxedInterface *iface);
static void visu_animatable_interface_init(VisuAnimatableInterface *iface);
static void visu_plane_get_property(GObject* obj, guint property_id,
                                    GValue *value, GParamSpec *pspec);
static void visu_plane_set_property(GObject* obj, guint property_id,
                                    const GValue *value, GParamSpec *pspec);

/* Local methods. */
static VisuBox* visu_plane_getBox(VisuBoxed *self);
static gboolean visu_plane_setBox(VisuBoxed *self, VisuBox *box);
static VisuAnimation* _getAnimation(const VisuAnimatable *self, const gchar *prop);
static void onBoxSizeChanged(VisuBox *box, float extens, gpointer data);
static int comparePolygonPoint(gconstpointer pointA, gconstpointer pointB, gpointer data);
static void computeInter(VisuPlane* plane);

G_DEFINE_TYPE_WITH_CODE(VisuPlane, visu_plane, VISU_TYPE_OBJECT,
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_BOXED,
                                              visu_boxed_interface_init)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_ANIMATABLE,
                                              visu_animatable_interface_init))

static void visu_plane_class_init(VisuPlaneClass *klass)
{
  DBG_fprintf(stderr, "Visu Plane : creating the class of the object.\n");

  /* Connect freeing methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_plane_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_plane_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_plane_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_plane_get_property;

  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuPlane::moved:
   * @plane: the object emitting the signal.
   *
   * This signal is emitted each time the plane position is changed
   * (either distance or normal).
   *
   * Since: 3.3
   */
  plane_signals[VISU_PLANE_MOVED_SIGNAL] =
    g_signal_newv("moved", G_TYPE_FROM_CLASS(klass),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
		  NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0, NULL);
  /**
   * VisuPlane::rendering:
   * @plane: the object emitting the signal.
   *
   * This signal is emitted each time the rendering properties (color,
   * visibility...) are affected.
   *
   * Since: 3.7
   */
  plane_signals[VISU_PLANE_RENDERING_SIGNAL] =
    g_signal_newv("rendering", G_TYPE_FROM_CLASS(klass),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
		  NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0, NULL);

  /**
   * VisuPlane::distance:
   *
   * Distance to origin.
   *
   * Since: 3.8
   */
  properties[DIST_PROP] = g_param_spec_float("distance", "Distance", "distance to origin",
                                             -G_MAXFLOAT, G_MAXFLOAT, 0.f, G_PARAM_READWRITE);
  /**
   * VisuPlane::n-vector:
   *
   * Normal vector (not necessary unitary).
   *
   * Since: 3.8
   */
  properties[VECT_PROP] = g_param_spec_boxed("n-vector", "NormalVector", "normal vector",
                                             TOOL_TYPE_VECTOR, G_PARAM_READWRITE);
  /**
   * VisuPlane::color:
   *
   * Rendering colour.
   *
   * Since: 3.8
   */
  properties[COLOR_PROP] = g_param_spec_boxed("color", "Color", "rendering color",
                                              TOOL_TYPE_COLOR, G_PARAM_READWRITE);
  /**
   * VisuPlane::hidding-side:
   *
   * If the plane is masking nodes or surfaces... And on which side.
   *
   * Since: 3.8
   */
  properties[HIDDING_PROP] = g_param_spec_int("hidding-side", "HiddingSide", "hidding property",
                                              VISU_PLANE_SIDE_MINUS, VISU_PLANE_SIDE_PLUS,
                                              VISU_PLANE_SIDE_NONE, G_PARAM_READWRITE);
  /**
   * VisuPlane::rendered:
   *
   * If the plane is drawn.
   *
   * Since: 3.8
   */
  properties[RENDERED_PROP] = g_param_spec_boolean("rendered", "Rendered", "rendering property",
                                                   TRUE, G_PARAM_READWRITE);
  /**
   * VisuPlane::opacity:
   *
   * The opacity the plane is drawn with.
   *
   * Since: 3.8
   */
  properties[OPACITY_PROP] = g_param_spec_float("opacity", "Opacity", "opacity property",
                                                0.f, 1.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");
}
static void visu_boxed_interface_init(VisuBoxedInterface *iface)
{
  iface->get_box = visu_plane_getBox;
  iface->set_box = visu_plane_setBox;
}
static void visu_animatable_interface_init(VisuAnimatableInterface *iface)
{
  iface->get_animation = _getAnimation;
}

static void visu_plane_init(VisuPlane *obj)
{
  obj->dispose_has_run = FALSE;

  DBG_fprintf(stderr, "Visu Plane : creating a new plane (%p).\n", (gpointer)obj);
  obj->inter = (GList*)0;
  obj->hiddenSide = VISU_PLANE_SIDE_NONE;
  obj->nVectUser[0] = 0.;
  obj->nVectUser[1] = 0.;
  obj->nVectUser[2] = 0.;
  obj->dist = 0.;
  obj->color = (ToolColor*)0;
  obj->box = (VisuBox*)0;
  obj->size_signal = 0;
  obj->opacity  = 1.f;
  obj->opacity_anim = visu_animation_new(G_OBJECT(obj), "opacity");
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_plane_dispose(GObject* obj)
{
  DBG_fprintf(stderr, "Visu Plane : dispose object %p.\n", (gpointer)obj);

  if (VISU_PLANE(obj)->dispose_has_run)
    return;

  visu_plane_setBox(VISU_BOXED(obj), (VisuBox*)0);
  g_object_unref(VISU_PLANE(obj)->opacity_anim);

  VISU_PLANE(obj)->dispose_has_run = TRUE;
  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_plane_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_plane_finalize(GObject* obj)
{
  GList *tmpLst;

  g_return_if_fail(obj);

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

  /* Deleting the intersection nodes if any. */
  tmpLst = VISU_PLANE(obj)->inter;
  while (tmpLst)
    {
      g_free(tmpLst->data);
      tmpLst = g_list_next(tmpLst);
    }
  if (VISU_PLANE(obj)->color)
    g_boxed_free(TOOL_TYPE_COLOR, VISU_PLANE(obj)->color);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_plane_parent_class)->finalize(obj);
}
static void visu_plane_get_property(GObject* obj, guint property_id,
                                    GValue *value, GParamSpec *pspec)
{
  VisuPlane *self = VISU_PLANE(obj);
  gfloat opacity;

  DBG_fprintf(stderr, "Visu Plane: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      g_value_set_object(value, self->box);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->box);
      break;
    case DIST_PROP:
      g_value_set_float(value, self->dist);
      DBG_fprintf(stderr, "%g.\n", self->dist);
      break;
    case VECT_PROP:
      g_value_set_static_boxed(value, (gconstpointer)self->nVectUser);
      DBG_fprintf(stderr, "%g,%g,%g.\n", self->nVectUser[0], self->nVectUser[1], self->nVectUser[2]);
      break;
    case COLOR_PROP:
      g_value_set_static_boxed(value, visu_plane_getColor(self));
      DBG_fprintf(stderr, "%g,%g,%g.\n", self->color->rgba[0], self->color->rgba[1], self->color->rgba[2]);
      break;
    case HIDDING_PROP:
      g_value_set_int(value, self->hiddenSide);
      DBG_fprintf(stderr, "%d.\n", self->hiddenSide);
      break;
    case RENDERED_PROP:
      g_object_get(self, "opacity", &opacity, NULL);
      g_value_set_boolean(value, opacity > 0.f);
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      break;
    case OPACITY_PROP:
      if (visu_animation_isRunning(self->opacity_anim))
        visu_animation_getTo(self->opacity_anim, value);
      else
        g_value_set_float(value, self->opacity);
      DBG_fprintf(stderr, "%g.\n", self->opacity);
      break;
    case ADJUST_PROP:
      g_value_set_boolean(value, FALSE);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_plane_set_property(GObject* obj, guint property_id,
                                    const GValue *value, GParamSpec *pspec)
{
  VisuPlane *self = VISU_PLANE(obj);

  DBG_fprintf(stderr, "Visu Plane: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_plane_setBox(VISU_BOXED(obj), VISU_BOX(g_value_get_object(value)));
      break;
    case DIST_PROP:
      visu_plane_setDistanceFromOrigin(self, g_value_get_float(value));
      DBG_fprintf(stderr, "%g.\n", self->dist);
      break;
    case VECT_PROP:
      visu_plane_setNormalVector(self, (float*)g_value_get_boxed(value));
      DBG_fprintf(stderr, "%g,%g,%g.\n", self->nVectUser[0], self->nVectUser[1], self->nVectUser[2]);
      break;
    case COLOR_PROP:
      visu_plane_setColor(self, (ToolColor*)g_value_get_boxed(value));
      DBG_fprintf(stderr, "%g,%g,%g (%p).\n", ((ToolColor*)g_value_get_boxed(value))->rgba[0],
                  ((ToolColor*)g_value_get_boxed(value))->rgba[1],
                  ((ToolColor*)g_value_get_boxed(value))->rgba[2], (gpointer)self->color);
      break;
    case HIDDING_PROP:
      visu_plane_setHiddenState(self, g_value_get_int(value));
      DBG_fprintf(stderr, "%d.\n", self->hiddenSide);
      break;
    case RENDERED_PROP:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      if (!visu_animatable_animateFloat(VISU_ANIMATABLE(self), self->opacity_anim,
                                        g_value_get_boolean(value) ? 1.f : 0.f,
                                        100, FALSE, VISU_ANIMATION_QUAD))
        visu_plane_setRendered(self, g_value_get_boolean(value));
      break;
    case ADJUST_PROP:
      DBG_fprintf(stderr, "%d.\n", FALSE);
      break;
    case OPACITY_PROP:
      if (!visu_animatable_animateFloat(VISU_ANIMATABLE(self), self->opacity_anim,
                                        g_value_get_float(value),
                                        100, FALSE, VISU_ANIMATION_QUAD))
        visu_plane_setOpacity(self, g_value_get_float(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_plane_newUndefined:
 *
 * Create a new plane with default values. This plane can't be
 * rendered directly and one needs to computes its intersection with
 * the bounding box before using planeComputeInter().
 *
 * Returns: (transfer full): a newly allocated #VisuPlane structure.
 */
VisuPlane* visu_plane_newUndefined(void)
{
  return g_object_new(VISU_TYPE_PLANE, NULL);
}

/**
 * visu_plane_new:
 * @box: (transfer full) (allow-none): a box description ;
 * @vect: (array fixed-size=3): three values defining the normal
 * vector (unitary or not) ;
 * @dist: the distance between origin and intersection of the plane and the
 * line made by origin and normal vector ;
 * @color: a #ToolColor.
 *
 * Create a plane with the specified attributes.
 *
 * Returns: a newly allocated #VisuPlane structure.
 */
VisuPlane* visu_plane_new(VisuBox *box, float vect[3], float dist,
                          const ToolColor *color)
{
  VisuPlane *plane;

  g_return_val_if_fail(color, (VisuPlane*)0);

  /* Create the object with defaulty values. */
  plane = VISU_PLANE(g_object_new(VISU_TYPE_PLANE, NULL));
  g_return_val_if_fail(plane, (VisuPlane*)0);

  visu_plane_setNormalVector(plane, vect);
  visu_plane_setDistanceFromOrigin(plane, dist);
  visu_plane_setBox(VISU_BOXED(plane), box);

  visu_plane_setColor(plane, color);
  
  return plane;
}

/**
 * visu_plane_setNormalVector:
 * @plane: a #VisuPlane object ;
 * @vect: (array fixed-size=3): three values defining the normal
 * vector (unitary or not).
 *
 * Change the normal vector defining the orientation of the plane.
 *
 * Returns: 1 if the intersections should be recalculated by
 *          a call to planeComputeInter(), 0 if not. Or -1 if there is
 *          an error.
 */
gboolean visu_plane_setNormalVector(VisuPlane *plane, float vect[3])
{
  int i;
  float norm;

  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  if (vect[0] == plane->nVectUser[0] &&
      vect[1] == plane->nVectUser[1] &&
      vect[2] == plane->nVectUser[2])
    return FALSE;
  g_return_val_if_fail(vect[0] * vect[0] +
		       vect[1] * vect[1] +
		       vect[2] * vect[2] != 0., FALSE);

  norm = 0.;
  for (i = 0; i < 3; i++)
    {
      norm += vect[i] * vect[i];
      plane->nVect[i] = vect[i];
      plane->nVectUser[i] = vect[i];
    }
  norm = sqrt(norm);
  for (i = 0; i < 3; i++)
    plane->nVect[i] /= norm;

  DBG_fprintf(stderr, "Visu Plane : set normal vector (%f,%f,%f) for plane %p.\n",
	      plane->nVect[0], plane->nVect[1], plane->nVect[2], (gpointer)plane);
  DBG_fprintf(stderr, "Visu Plane : set user vector (%f,%f,%f) for plane %p.\n",
	      plane->nVectUser[0], plane->nVectUser[1], plane->nVectUser[2],
	      (gpointer)plane);

  g_object_notify_by_pspec(G_OBJECT(plane), properties[VECT_PROP]);

  if (!plane->box)
    return TRUE;

  /* We recompute the intersection. */
  computeInter(plane);
  return TRUE;
}

gboolean visu_plane_setDistanceFromOrigin(VisuPlane *plane, float dist)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  if (plane->dist == dist)
    return FALSE;

  plane->dist = dist;
  DBG_fprintf(stderr, "Visu Plane : set distance from origin %f for plane %p.\n",
	      dist, (gpointer)plane);

  g_object_notify_by_pspec(G_OBJECT(plane), properties[DIST_PROP]);

  if (!plane->box)
    return TRUE;

  /* We recompute the intersection. */
  computeInter(plane);
  return TRUE;
}

/**
 * visu_plane_setOrigin:
 * @plane: a #VisuPlane object.
 * @origin: (array fixed-size=3): some cartesian coordinates.
 *
 * Defines a point belonging to @plane instead of defining distance of
 * @plane to the box origin.
 *
 * Since: 3.8
 *
 * Returns: TRUE if origin is actually changed.
 **/
gboolean visu_plane_setOrigin(VisuPlane *plane, const float origin[3])
{
  float dist;
  
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  if (plane->nVect[0] == 0.f && plane->nVect[1] == 0.f && plane->nVect[2] == 0.f)
    return FALSE;
  
  dist = plane->nVect[0] * origin[0] + plane->nVect[1] * origin[1]
    + plane->nVect[2] * origin[2];
  return visu_plane_setDistanceFromOrigin(plane, dist);
}

static gboolean visu_plane_setBox(VisuBoxed *self, VisuBox *box)
{
  VisuPlane *plane;

  g_return_val_if_fail(VISU_IS_PLANE(self), FALSE);
  plane = VISU_PLANE(self);

  DBG_fprintf(stderr, "Visu Plane: set box %p.\n", (gpointer)box);

  if (plane->box == box)
    return FALSE;

  if (plane->box)
    {
      g_signal_handler_disconnect(G_OBJECT(plane->box), plane->size_signal);
      g_object_unref(plane->box);
    }
  plane->box = box;
  if (!box)
    return TRUE;

  g_object_ref(box);
  plane->size_signal = g_signal_connect(G_OBJECT(box), "SizeChanged",
                                        G_CALLBACK(onBoxSizeChanged), (gpointer)plane);
  computeInter(plane);

  return TRUE;
}
static VisuBox* visu_plane_getBox(VisuBoxed *self)
{
  g_return_val_if_fail(VISU_IS_PLANE(self), (VisuBox*)0);

  return VISU_PLANE(self)->box;
}

/**
 * visu_plane_setColor:
 * @plane: a #VisuPlane object ;
 * @color: a #ToolColor.
 *
 * Change the color of the plane.
 *
 * Returns: TRUE if color changed.
 */
gboolean visu_plane_setColor(VisuPlane *plane, const ToolColor *color)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  if (tool_color_equal(color, plane->color))
    return FALSE;

  DBG_fprintf(stderr, "Visu Plane: changed color (%p) for plane : %p.\n",
	      (gpointer)color, (gpointer)plane);
  plane->color = g_boxed_copy(TOOL_TYPE_COLOR, color);

  g_object_notify_by_pspec(G_OBJECT(plane), properties[COLOR_PROP]);
  g_signal_emit(G_OBJECT(plane), plane_signals[VISU_PLANE_RENDERING_SIGNAL], 0, NULL);
  return TRUE;
}

/**
 * visu_plane_getOpacity:
 * @plane: a #VisuPlane object.
 *
 * Retrieves the plane opacity.
 *
 * Since: 3.8
 *
 * Returns: a float within [0;1].
 **/
gfloat visu_plane_getOpacity(const VisuPlane *plane)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), 1.f);

  return plane->opacity;
}

/**
 * visu_plane_setOpacity:
 * @plane: a #VisuPlane object.
 * @opacity: a float in [0;1].
 *
 * Defines the plane opacity. Planes can be coloured with alpha
 * channel colours. The opacity is an additional parameter that can be
 * used to animate plane apparition for instance.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_plane_setOpacity(VisuPlane *plane, gfloat opacity)
{
  gboolean renderedChanged;
  
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  opacity = CLAMP(opacity, 0.f, 1.f);
  if (opacity == plane->opacity)
    return FALSE;

  renderedChanged = (plane->opacity == 0.f && opacity > 0.f) ||
    (plane->opacity > 0.f && opacity == 0.f);
  plane->opacity = opacity;
  g_object_notify_by_pspec(G_OBJECT(plane), properties[OPACITY_PROP]);
  if (renderedChanged)
    g_object_notify_by_pspec(G_OBJECT(plane), properties[RENDERED_PROP]);
  g_signal_emit(G_OBJECT(plane), plane_signals[VISU_PLANE_RENDERING_SIGNAL], 0, NULL);
  return TRUE;
}

/**
 * visu_plane_setRendered:
 * @plane: a #VisuPlane ;
 * @rendered: TRUE to make the plane drawable.
 *
 * Change the visibility of the plane.
 *
 * Returns:  TRUE if the visibility is actually changed.
 */
gboolean visu_plane_setRendered(VisuPlane *plane, gboolean rendered)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  if (visu_animation_isRunning(plane->opacity_anim))
    visu_animation_abort(plane->opacity_anim);
  else if (rendered == (plane->opacity > 0.f))
    return FALSE;
    
  DBG_fprintf(stderr, "Visu Plane: change plane visibility to %d.\n", rendered);
  return visu_plane_setOpacity(plane, rendered ? 1.f : 0.f);
}
/**
 * visu_plane_getRendered:
 * @plane: a #VisuPlane.
 *
 * Get the visibility of a plane.
 *
 * Returns: TRUE if the plane is visible.
 */
gboolean visu_plane_getRendered(const VisuPlane *plane)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  return (plane->opacity > 0.f);
}
/**
 * visu_plane_getIntersection:
 * @plane: a #VisuPlane.
 *
 * The list of intersection between the plane and the box is made of
 * float[3]. The planeComputeInter() should have been called before.
 *
 * Returns: (transfer none) (element-type ToolVector): a list of float[3]
 * elements. This list is owned by V_Sim.
 */
GList* visu_plane_getIntersection(const VisuPlane *plane)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane) && plane->box, (GList*)0);

  return plane->inter;
}
/**
 * visu_plane_getBasis:
 * @plane: a #VisuPlane ;
 * @xyz: two vectors.
 * @center: a point in cartesian coordinates.
 *
 * Stores the coordinates of barycentre of the plane in @center and
 * provide coordinates of two orthogonal vector in the plane. The planeComputeInter()
 * should have been called before.
 */
void visu_plane_getBasis(const VisuPlane *plane, float xyz[2][3], float center[3])
{
  float spherical[3];

  g_return_if_fail(VISU_IS_PLANE(plane));

  tool_matrix_cartesianToSpherical(spherical, plane->nVectUser);
  xyz[0][0] = cos(spherical[1] * TOOL_PI180) * cos(spherical[2] * TOOL_PI180);
  xyz[0][1] = cos(spherical[1] * TOOL_PI180) * sin(spherical[2] * TOOL_PI180);
  xyz[0][2] = -sin(spherical[1] * TOOL_PI180);
  xyz[1][0] = -sin(spherical[2] * TOOL_PI180);
  xyz[1][1] =  cos(spherical[2] * TOOL_PI180);
  xyz[1][2] = 0.;
  center[0] = plane->pointG[0];
  center[1] = plane->pointG[1];
  center[2] = plane->pointG[2];
}
/**
 * visu_plane_getReducedIntersection:
 * @plane: a #VisuPlane object.
 * @nVals: a location for an integer.
 *
 * This routine returns the coordinates in the @plane basis set of its
 * intersections with a box (see visu_boxed_setBox()). The coordinates are
 * appended in the return array which length is stored in @nVals.
 *
 * Since: 3.6
 *
 * Returns: a newly allocated array of @nVals * 2 values. Free it with
 * g_free().
 */
float* visu_plane_getReducedIntersection(const VisuPlane *plane, guint *nVals)
{
  float *out, basis[2][3], center[3], *xyz;
  GList *tmpLst;
  gint i;

  g_return_val_if_fail(VISU_IS_PLANE(plane) && plane->box, (float*)0);
  g_return_val_if_fail(nVals, (float*)0);

  if (!plane->inter)
    return (float*)0;

  visu_plane_getBasis(plane, basis, center);
  
  i = 0;
  out = g_malloc(sizeof(float) * g_list_length(plane->inter) * 2);
  for (tmpLst = plane->inter; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      xyz = (float*)tmpLst->data;
      out[i * 2 + 0] =
	basis[0][0] * (xyz[0] - center[0]) +
	basis[0][1] * (xyz[1] - center[1]) +
	basis[0][2] * (xyz[2] - center[2]);
      out[i * 2 + 1] =
	basis[1][0] * (xyz[0] - center[0]) +
	basis[1][1] * (xyz[1] - center[1]) +
	basis[1][2] * (xyz[2] - center[2]);
      i += 1;
    }
  *nVals = i;

  return out;
}

/**
 * visu_plane_getLineIntersection:
 * @plane: a #VisuPlane object.
 * @A: (array fixed-size=3): coordinates of point A.
 * @B: (array fixed-size=3): coordinates of point B.
 * @lambda: (out caller-allocates) (allow-none): a location to store
 * the intersecting factor.
 *
 * If there is an intersection M between line (AB) and @plane, then this
 * function calculates M coordinates as M = A + lambda * AB.
 *
 * Since: 3.6
 *
 * Returns: TRUE if there is an intersection between line (AB) and the plane.
 **/
gboolean visu_plane_getLineIntersection(const VisuPlane *plane, const float A[3],
                                        const float B[3], float *lambda)
{
  float denom, lambda_;

  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);

  /*
    The plane is defined by n1x+n2y+n3z-d=0 with (n1,n2,n3) the normal vector (unitary)
    and d the algebric distance from the origin.
    A segment {P} of the box is defined by P=A+lambda.l with A a vertex, l a vector
    in the direction of the segment and lambda a real.
    If it exists a lambda that can solve P(lambda) in the plane equation, and this lambda
    is in [0;1] then this is the intersection.
   */
  lambda_  = plane->dist;
  lambda_ -= plane->nVect[0] * A[0];
  lambda_ -= plane->nVect[1] * A[1];
  lambda_ -= plane->nVect[2] * A[2];

  denom  = plane->nVect[0] * (B[0] - A[0]);
  denom += plane->nVect[1] * (B[1] - A[1]);
  denom += plane->nVect[2] * (B[2] - A[2]);
  if (denom == 0.)
    {
      /* if A is in the plane, we put lambda to 0. */
      if (lambda_ == 0.f)
        denom = 1.f;
      else
        return FALSE;
    }

  lambda_ /= denom;
  if (lambda)
    *lambda = lambda_;

  return TRUE;
}

static void computeInter(VisuPlane* plane)
{
  float lambda;
  int a[12] = {0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7};
  int b[12] = {1, 2, 3, 0, 4, 5, 6, 7, 5, 6, 7, 4};
  int i, j, n;
  float *inter;
  GList *tmpLst;
  float vertices[8][3];

  g_return_if_fail(VISU_IS_PLANE(plane) && plane->box);

  /* RAZ old intersection list. */
  if (plane->inter)
    {
      tmpLst = plane->inter;
      while(tmpLst)
	{
	  g_free((float*)tmpLst->data);
	  tmpLst = g_list_next(tmpLst);
	}
      g_list_free(plane->inter);
      plane->inter = (GList*)0;
    }
  /* Compute, vector and position for the box. */
  n = 0;
  plane->pointG[0] = 0.;
  plane->pointG[1] = 0.;
  plane->pointG[2] = 0.;
  visu_box_getVertices(plane->box, vertices, TRUE);
  for (i = 0; i < 12; i++)
    if (visu_plane_getLineIntersection(plane, vertices[a[i]], vertices[b[i]], &lambda) &&
        lambda >= 0. && lambda <= 1.)
      {
        inter = g_malloc(sizeof(float) * 3);
        for (j = 0; j < 3; j++)
          {
            inter[j] = vertices[a[i]][j] + lambda * (vertices[b[i]][j] -
                                                     vertices[a[i]][j]);
            plane->pointG[j] += inter[j];
          }
        n += 1;
        plane->inter = g_list_append(plane->inter, (gpointer)inter);
        DBG_fprintf(stderr, "Visu Plane: a new intersection (%f,%f,%f) for plane %p.\n",
                    inter[0], inter[1], inter[2], (gpointer)plane);
      }
  if (n > 0)
    {
      for (i = 0; i < 3; i++)
	plane->pointG[i] /= (float)n;
      plane->inter = g_list_sort_with_data(plane->inter, comparePolygonPoint, (gpointer)plane);
    }

  DBG_fprintf(stderr, "Visu Plane: emit 'plane moved' signal.\n");
  g_signal_emit(G_OBJECT(plane), plane_signals[VISU_PLANE_MOVED_SIGNAL], 0, NULL);
}

/**
 * visu_plane_getPlaneIntersection:
 * @plane1: a #VisuPlane object.
 * @plane2: another #VisuPlane object.
 * @A: (out) (array fixed-size=3): the coordinates of the first point
 * of intersection.
 * @B: (out) (array fixed-size=3): the coordinates of the second point
 * of intersection.
 *
 * Calculates the intersection between @plane1 and @plane2, if it
 * exists. The intersection is returned in @A and @B as the
 * coordinates of the two points on the border of @plane1 that
 * intersect @plane2. 
 *
 * Since: 3.7
 *
 * Returns: TRUE if there is an intersection between @plane1 and @plane2.
 **/
gboolean visu_plane_getPlaneIntersection(const VisuPlane *plane1, const VisuPlane *plane2,
                                         float A[3], float B[3])
{
  float lambda, *I, *J;
  GList *inter;
  guint n;
  float M[8][3];
  
  n = 0;
  for (inter = plane1->inter; inter; inter = g_list_next(inter))
    {
      g_return_val_if_fail(n < 8, FALSE);
      I = (float*)inter->data;
      J = (inter->next)?(float*)inter->next->data:(float*)plane1->inter->data;
      if (visu_plane_getLineIntersection(plane2, I, J, &lambda) &&
          lambda >= 0.f && lambda <= 1.f)
        {
          M[n][0] = I[0] + lambda * (J[0] - I[0]);
          M[n][1] = I[1] + lambda * (J[1] - I[1]);
          M[n][2] = I[2] + lambda * (J[2] - I[2]);
          n += 1;
        }
    }
  if (n != 2)
    return FALSE;

  A[0] = M[0][0];
  A[1] = M[0][1];
  A[2] = M[0][2];

  B[0] = M[1][0];
  B[1] = M[1][1];
  B[2] = M[1][2];

  fprintf(stderr, "%g %g %g    |    %g %g %g\n", A[0], A[1], A[2], B[0], B[1], B[2]);

  return TRUE;
}

static int comparePolygonPoint(gconstpointer pointA, gconstpointer pointB, gpointer data)
{
  VisuPlane *plane;
  float vectGA[3], vectGB[3];
  int i;
  float det;

  plane = (VisuPlane*)data;
  for (i = 0; i < 3; i++)
    {
      vectGA[i] = ((float*)pointA)[i] - plane->pointG[i];
      vectGB[i] = ((float*)pointB)[i] - plane->pointG[i];
    }
  det = vectGA[0] * vectGB[1] * plane->nVect[2] + vectGB[0] * plane->nVect[1] * vectGA[2] +
    plane->nVect[0] * vectGA[1] * vectGB[2] - vectGA[2] * vectGB[1] * plane->nVect[0] -
    vectGA[1] * vectGB[0] * plane->nVect[2] - vectGA[0] * vectGB[2] * plane->nVect[1];
  if (det < 0.)
    return -1;
  else if (det > 0.)
    return 1;
  else
    return 0;
}

void visu_plane_getNVectUser(const VisuPlane *plane, float vect[3])
{
  g_return_if_fail(VISU_IS_PLANE(plane));

  vect[0] = plane->nVectUser[0];
  vect[1] = plane->nVectUser[1];
  vect[2] = plane->nVectUser[2];
}

void visu_plane_getNVect(const VisuPlane *plane, float vect[3])
{
  g_return_if_fail(VISU_IS_PLANE(plane));

  vect[0] = plane->nVect[0];
  vect[1] = plane->nVect[1];
  vect[2] = plane->nVect[2];
}
/**
 * visu_plane_getColor:
 * @plane: a #VisuPlane ;
 *
 * Stores the color of the plane.
 *
 * Returns: (transfer none): a #ToolColor.
 */
const ToolColor* visu_plane_getColor(VisuPlane *plane)
{
  static guint id = 0;
  
  g_return_val_if_fail(VISU_IS_PLANE(plane), (ToolColor*)0);

  if (!plane->color)
    visu_plane_setColor(plane, tool_color_new_bright(id++));
  return plane->color;
}
/**
 * visu_plane_getDistanceFromOrigin:
 * @plane: a #VisuPlane ;
 *
 * Stores the distance of the plane to the origin.
 *
 * Returns: a float value.
 */
gfloat visu_plane_getDistanceFromOrigin(const VisuPlane *plane)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), 0.f);

  return plane->dist;
}
/**
 * visu_plane_setHiddenState:
 * @plane: a #VisuPlane ;
 * @side: a key, #VISU_PLANE_SIDE_NONE, #VISU_PLANE_SIDE_PLUS or #VISU_PLANE_SIDE_MINUS.
 *
 * The plane can hide the nodes on one of its side.
 * The @side argument can be VISU_PLANE_SIDE_PLUS or VISU_PLANE_SIDE_MINUS or
 * VISU_PLANE_SIDE_NONE. It codes the side of the plane which hides the nodes.
 * If VISU_PLANE_SIDE_NONE is selected all nodes are rendered.
 * 
 * Returns: TRUE if the hidding side was actually changed.
 */
gboolean visu_plane_setHiddenState(VisuPlane *plane, int side)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), FALSE);
  g_return_val_if_fail(side == VISU_PLANE_SIDE_NONE ||
		       side == VISU_PLANE_SIDE_PLUS ||
		       side == VISU_PLANE_SIDE_MINUS, FALSE);

  DBG_fprintf(stderr, "Visu Plane : hide state (%d, old %d) for plane %p.\n",
	      side, plane->hiddenSide, (gpointer)plane);
  if (plane->hiddenSide == side)
    return FALSE;
  plane->hiddenSide = side;
  g_object_notify_by_pspec(G_OBJECT(plane), properties[HIDDING_PROP]);
  return TRUE;
}
int visu_plane_getHiddenState(const VisuPlane *plane)
{
  g_return_val_if_fail(VISU_IS_PLANE(plane), 0);
  
  return plane->hiddenSide;
}

/**
 * visu_plane_getVisibility:
 * @plane: a #VisuPlane object.
 * @point: (array fixed-size=3): some coordinates.
 *
 * Test the visibility of a given point with respect to the plane attributes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the given point is not masked by the @plane.
 **/
gboolean visu_plane_getVisibility(const VisuPlane *plane, float point[3])
{
  float pScal;

  g_return_val_if_fail(VISU_IS_PLANE(plane), TRUE);

  pScal = plane->nVect[0] * point[0] + plane->nVect[1] * point[1] + plane->nVect[2] * point[2] -
    plane->dist;
  /* DBG_fprintf(stderr, "Visu plane: test point %g %g %g -> %d\n", point[0], point[1], point[2], */
  /*             (pScal * plane->hiddenSide >= 0.)); */
  return (pScal * plane->hiddenSide >= 0.);

}

static void onBoxSizeChanged(VisuBox *box _U_, float extens _U_, gpointer data)
{
  computeInter(VISU_PLANE(data));
}

static VisuAnimation* _getAnimation(const VisuAnimatable *self, const gchar *prop)
{
  VisuPlane *plane = VISU_PLANE(self);

  if (!strcmp(prop, "opacity"))
    return plane->opacity_anim;
  return (VisuAnimation*)0;
}

static gint comparisonForSortingFloats(gconstpointer a, gconstpointer b,
				       gpointer user_data _U_)
{
  float c;

  c = *((float*)b) - *((float*)a);
  if (c < 0.f)
    return  (gint)(1);
  else if (c > 0.f)
    return (gint)(-1);
  else
    return (gint)(0);
}

static gint comparisonForHavingIndices(gconstpointer a, gconstpointer b,
				       gpointer user_data)
{
  float c;
  float *array;

  array = (float*)user_data;
  /*DBG_fprintf(stderr, "INDICES : a %d    b %d\n", *((int*)a), *((int*)b));*/
  c = array[*(int*)b] - array[*(int*)a];
  /*DBG_fprintf(stderr, "LAMBDA b (%f) - LAMBDA a (%f) :  %f\n", vb, va,c);*/
  if (c < 0.f)
    return  (gint)(1);
  else if (c > 0.f)
    return (gint)(-1);
  else
    return (gint)(0);
}

gboolean visu_plane_class_getOrderedIntersections(int nVisuPlanes, VisuPlane **listOfVisuPlanes,
					float pointA[3], float pointB[3],
					float *inter, int *index)
{
  float *lambda;
  int *indices;
  int i;

  lambda = g_malloc(sizeof(float) * nVisuPlanes);
  indices = g_malloc(sizeof(int) * nVisuPlanes);
  for (i = 0; listOfVisuPlanes[i]; i++)
    {
      indices[i] = i;
      if (!visu_plane_getLineIntersection(listOfVisuPlanes[i], pointA, pointB, lambda + i) ||
          lambda[i] < 0. || lambda[i] > 1.)
        /* If lambda is not between 0 and 1, this means that the plane i is not
           intersected by the AB segment => return FALSE */
        return FALSE;
    }
  /* Sort lambda */
  DBG_fprintf(stderr, "Sorting planes by lambda :\n  ... lambda unsorted : ");
  for (i = 0; i < nVisuPlanes; i++)
    DBG_fprintf(stderr, "%f ", lambda[i]);
  DBG_fprintf(stderr, "\n");
  g_qsort_with_data(indices, nVisuPlanes, sizeof(int),
                    comparisonForHavingIndices, (float*)lambda);
  g_qsort_with_data(lambda, nVisuPlanes, sizeof(float),
                    comparisonForSortingFloats, NULL);
  DBG_fprintf(stderr, "  ... lambda sorted   : ");
  for (i = 0; i < nVisuPlanes; i++)
    DBG_fprintf(stderr, "%f ", lambda[i]);
  DBG_fprintf(stderr, "\n  ... indices : ");
  for (i = 0; i < nVisuPlanes; i++)
    DBG_fprintf(stderr, "%d ", indices[i]);
  DBG_fprintf(stderr, "\n");

  /* Put the intersections in inter in the right order. */
  DBG_fprintf(stderr, "  ... intersections sorted :\n");
  for (i = 0; i < nVisuPlanes; i++)
  {
    *(inter + 3*i) = pointA[0] + lambda[i] * (pointB[0] - pointA[0]);
    *(inter + 3*i + 1) = pointA[1] + lambda[i] * (pointB[1] - pointA[1]);
    *(inter + 3*i + 2) = pointA[2] + lambda[i] * (pointB[2] - pointA[2]);
    DBG_fprintf(stderr, "    %f %f %f\n", *(inter + 3*i), *(inter + 3*i + 1), *(inter + 3*i + 2));
    *(index + i) = indices[i];
  }

  g_free(lambda);
  g_free(indices);
  return TRUE;
}
