/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Authored By Matthew Allum  <mallum@openedhand.com>
 *
 * Copyright (C) 2006 OpenedHand
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:clutter-geometric-types
 * @Title: Base geometric types
 * @Short_Description: Common geometric data types used by Clutter
 *
 * Clutter defines a set of geometric data structures that are commonly used
 * across the whole API.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-types.h"
#include "clutter-private.h"

#include <math.h>

#define FLOAT_EPSILON   (1e-15)



/*
 * ClutterGeometry
 */

static ClutterGeometry*
clutter_geometry_copy (const ClutterGeometry *geometry)
{
  return g_slice_dup (ClutterGeometry, geometry);
}

static void
clutter_geometry_free (ClutterGeometry *geometry)
{
  if (G_LIKELY (geometry != NULL))
    g_slice_free (ClutterGeometry, geometry);
}

/**
 * clutter_geometry_union:
 * @geometry_a: a #ClutterGeometry
 * @geometry_b: another #ClutterGeometry
 * @result: (out): location to store the result
 *
 * Find the union of two rectangles represented as #ClutterGeometry.
 *
 * Since: 1.4
 */
void
clutter_geometry_union (const ClutterGeometry *geometry_a,
                        const ClutterGeometry *geometry_b,
                        ClutterGeometry       *result)
{
  /* We don't try to handle rectangles that can't be represented
   * as a signed integer box */
  gint x_1 = MIN (geometry_a->x, geometry_b->x);
  gint y_1 = MIN (geometry_a->y, geometry_b->y);
  gint x_2 = MAX (geometry_a->x + (gint)geometry_a->width,
                  geometry_b->x + (gint)geometry_b->width);
  gint y_2 = MAX (geometry_a->y + (gint)geometry_a->height,
                  geometry_b->y + (gint)geometry_b->height);
  result->x = x_1;
  result->y = y_1;
  result->width = x_2 - x_1;
  result->height = y_2 - y_1;
}

/**
 * clutter_geometry_intersects:
 * @geometry0: The first geometry to test
 * @geometry1: The second geometry to test
 *
 * Determines if @geometry0 and geometry1 intersect returning %TRUE if
 * they do else %FALSE.
 *
 * Return value: %TRUE of @geometry0 and geometry1 intersect else
 * %FALSE.
 *
 * Since: 1.4
 */
gboolean
clutter_geometry_intersects (const ClutterGeometry *geometry0,
                             const ClutterGeometry *geometry1)
{
  if (geometry1->x >= (geometry0->x + (gint)geometry0->width) ||
      geometry1->y >= (geometry0->y + (gint)geometry0->height) ||
      (geometry1->x + (gint)geometry1->width) <= geometry0->x ||
      (geometry1->y + (gint)geometry1->height) <= geometry0->y)
    return FALSE;
  else
    return TRUE;
}

static gboolean
clutter_geometry_progress (const GValue *a,
                           const GValue *b,
                           gdouble       progress,
                           GValue       *retval)
{
  const ClutterGeometry *a_geom = g_value_get_boxed (a);
  const ClutterGeometry *b_geom = g_value_get_boxed (b);
  ClutterGeometry res = { 0, };
  gint a_width = a_geom->width;
  gint b_width = b_geom->width;
  gint a_height = a_geom->height;
  gint b_height = b_geom->height;

  res.x = a_geom->x + (b_geom->x - a_geom->x) * progress;
  res.y = a_geom->y + (b_geom->y - a_geom->y) * progress;

  res.width = a_width + (b_width - a_width) * progress;
  res.height = a_height + (b_height - a_height) * progress;

  g_value_set_boxed (retval, &res);

  return TRUE;
}

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterGeometry, clutter_geometry,
                               clutter_geometry_copy,
                               clutter_geometry_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_geometry_progress));



/*
 * ClutterVertices
 */

/**
 * clutter_vertex_new:
 * @x: X coordinate
 * @y: Y coordinate
 * @z: Z coordinate
 *
 * Creates a new #ClutterVertex for the point in 3D space
 * identified by the 3 coordinates @x, @y, @z.
 *
 * This function is the logical equivalent of:
 *
 * |[
 *   clutter_vertex_init (clutter_vertex_alloc (), x, y, z);
 * ]|
 *
 * Return value: (transfer full): the newly allocated #ClutterVertex.
 *   Use clutter_vertex_free() to free the resources
 *
 * Since: 1.0
 */
ClutterVertex *
clutter_vertex_new (gfloat x,
                    gfloat y,
                    gfloat z)
{
  return clutter_vertex_init (clutter_vertex_alloc (), x, y, z);
}

/**
 * clutter_vertex_alloc:
 *
 * Allocates a new, empty #ClutterVertex.
 *
 * Return value: (transfer full): the newly allocated #ClutterVertex.
 *   Use clutter_vertex_free() to free its resources
 *
 * Since: 1.12
 */
ClutterVertex *
clutter_vertex_alloc (void)
{
  return g_slice_new0 (ClutterVertex);
}

/**
 * clutter_vertex_init:
 * @vertex: a #ClutterVertex
 * @x: X coordinate
 * @y: Y coordinate
 * @z: Z coordinate
 *
 * Initializes @vertex with the given coordinates.
 *
 * Return value: (transfer none): the initialized #ClutterVertex
 *
 * Since: 1.10
 */
ClutterVertex *
clutter_vertex_init (ClutterVertex *vertex,
                     gfloat         x,
                     gfloat         y,
                     gfloat         z)
{
  g_return_val_if_fail (vertex != NULL, NULL);

  vertex->x = x;
  vertex->y = y;
  vertex->z = z;

  return vertex;
}

/**
 * clutter_vertex_copy:
 * @vertex: a #ClutterVertex
 *
 * Copies @vertex
 *
 * Return value: (transfer full): a newly allocated copy of #ClutterVertex.
 *   Use clutter_vertex_free() to free the allocated resources
 *
 * Since: 1.0
 */
ClutterVertex *
clutter_vertex_copy (const ClutterVertex *vertex)
{
  if (G_LIKELY (vertex != NULL))
    return g_slice_dup (ClutterVertex, vertex);

  return NULL;
}

/**
 * clutter_vertex_free:
 * @vertex: a #ClutterVertex
 *
 * Frees a #ClutterVertex allocated using clutter_vertex_alloc() or
 * clutter_vertex_copy().
 *
 * Since: 1.0
 */
void
clutter_vertex_free (ClutterVertex *vertex)
{
  if (G_UNLIKELY (vertex != NULL))
    g_slice_free (ClutterVertex, vertex);
}

/**
 * clutter_vertex_equal:
 * @vertex_a: a #ClutterVertex
 * @vertex_b: a #ClutterVertex
 *
 * Compares @vertex_a and @vertex_b for equality
 *
 * Return value: %TRUE if the passed #ClutterVertex are equal
 *
 * Since: 1.0
 */
gboolean
clutter_vertex_equal (const ClutterVertex *vertex_a,
                      const ClutterVertex *vertex_b)
{
  g_return_val_if_fail (vertex_a != NULL && vertex_b != NULL, FALSE);

  if (vertex_a == vertex_b)
    return TRUE;

  return fabsf (vertex_a->x - vertex_b->x) < FLOAT_EPSILON &&
         fabsf (vertex_a->y - vertex_b->y) < FLOAT_EPSILON &&
         fabsf (vertex_a->z - vertex_b->z) < FLOAT_EPSILON;
}

static gboolean
clutter_vertex_progress (const GValue *a,
                         const GValue *b,
                         gdouble       progress,
                         GValue       *retval)
{
  const ClutterVertex *av = g_value_get_boxed (a);
  const ClutterVertex *bv = g_value_get_boxed (b);
  ClutterVertex res;

  res.x = av->x + (bv->x - av->x) * progress;
  res.y = av->y + (bv->y - av->y) * progress;
  res.z = av->z + (bv->z - av->z) * progress;

  g_value_set_boxed (retval, &res);

  return TRUE;
}

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterVertex, clutter_vertex,
                               clutter_vertex_copy,
                               clutter_vertex_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_vertex_progress));



/*
 * ClutterMargin
 */

/**
 * clutter_margin_new:
 *
 * Creates a new #ClutterMargin.
 *
 * Return value: (transfer full): a newly allocated #ClutterMargin. Use
 *   clutter_margin_free() to free the resources associated with it when
 *   done.
 *
 * Since: 1.10
 */
ClutterMargin *
clutter_margin_new (void)
{
  return g_slice_new0 (ClutterMargin);
}

/**
 * clutter_margin_copy:
 * @margin_: a #ClutterMargin
 *
 * Creates a new #ClutterMargin and copies the contents of @margin_ into
 * the newly created structure.
 *
 * Return value: (transfer full): a copy of the #ClutterMargin.
 *
 * Since: 1.10
 */
ClutterMargin *
clutter_margin_copy (const ClutterMargin *margin_)
{
  if (G_LIKELY (margin_ != NULL))
    return g_slice_dup (ClutterMargin, margin_);

  return NULL;
}

/**
 * clutter_margin_free:
 * @margin_: a #ClutterMargin
 *
 * Frees the resources allocated by clutter_margin_new() and
 * clutter_margin_copy().
 *
 * Since: 1.10
 */
void
clutter_margin_free (ClutterMargin *margin_)
{
  if (G_LIKELY (margin_ != NULL))
    g_slice_free (ClutterMargin, margin_);
}

G_DEFINE_BOXED_TYPE (ClutterMargin, clutter_margin,
                     clutter_margin_copy,
                     clutter_margin_free)



/*
 * ClutterPoint
 */

/**
 * clutter_point_alloc:
 *
 * Allocates a new #ClutterPoint.
 *
 * Return value: (transfer full): the newly allocated #ClutterPoint.
 *   Use clutter_point_free() to free its resources.
 *
 * Since: 1.12
 */
ClutterPoint *
clutter_point_alloc (void)
{
  return g_slice_new0 (ClutterPoint);
}

/**
 * clutter_point_init:
 * @point: a #ClutterPoint
 * @x: the X coordinate of the point
 * @y: the Y coordinate of the point
 *
 * Initializes @point with the given coordinates.
 *
 * Return value: (transfer none): the initialized #ClutterPoint
 *
 * Since: 1.12
 */
ClutterPoint *
clutter_point_init (ClutterPoint *point,
                    float         x,
                    float         y)
{
  g_return_val_if_fail (point != NULL, NULL);

  point->x = x;
  point->y = y;

  return point;
}

/**
 * clutter_point_copy:
 * @point: a #ClutterPoint
 *
 * Creates a new #ClutterPoint with the same coordinates of @point.
 *
 * Return value: (transfer full): a newly allocated #ClutterPoint.
 *   Use clutter_point_free() to free its resources.
 *
 * Since: 1.12
 */
ClutterPoint *
clutter_point_copy (const ClutterPoint *point)
{
  return g_slice_dup (ClutterPoint, point);
}

/**
 * clutter_point_free:
 * @point: a #ClutterPoint
 *
 * Frees the resources allocated for @point.
 *
 * Since: 1.12
 */
void
clutter_point_free (ClutterPoint *point)
{
  if (point != NULL)
    g_slice_free (ClutterPoint, point);
}

/**
 * clutter_point_equals:
 * @a: the first #ClutterPoint to compare
 * @b: the second #ClutterPoint to compare
 *
 * Compares two #ClutterPoint for equality.
 *
 * Return value: %TRUE if the #ClutterPoints are equal
 *
 * Since: 1.12
 */
gboolean
clutter_point_equals (const ClutterPoint *a,
                      const ClutterPoint *b)
{
  if (a == b)
    return TRUE;

  if (a == NULL || b == NULL)
    return FALSE;

  return fabsf (a->x - b->x) < FLOAT_EPSILON &&
         fabsf (a->y - b->y) < FLOAT_EPSILON;
}

/**
 * clutter_point_distance:
 * @a: a #ClutterPoint
 * @b: a #ClutterPoint
 * @x_distance: (out) (allow-none): return location for the horizontal
 *   distance between the points
 * @y_distance: (out) (allow-none): return location for the vertical
 *   distance between the points
 *
 * Computes the distance between two #ClutterPoint.
 *
 * Return value: the distance between the points.
 *
 * Since: 1.12
 */
float
clutter_point_distance (const ClutterPoint *a,
                        const ClutterPoint *b,
                        float              *x_distance,
                        float              *y_distance)
{
  float x_d, y_d;

  g_return_val_if_fail (a != NULL, 0.f);
  g_return_val_if_fail (b != NULL, 0.f);

  if (clutter_point_equals (a, b))
    return 0.f;

  x_d = (a->x - b->x);
  y_d = (a->y - b->y);

  if (x_distance != NULL)
    *x_distance = fabsf (x_d);

  if (y_distance != NULL)
    *y_distance = fabsf (y_d);

  return sqrt ((x_d * x_d) + (y_d * y_d));
}

static gboolean
clutter_point_progress (const GValue *a,
                        const GValue *b,
                        gdouble       progress,
                        GValue       *retval)
{
  const ClutterPoint *ap = g_value_get_boxed (a);
  const ClutterPoint *bp = g_value_get_boxed (b);
  ClutterPoint res = CLUTTER_POINT_INIT (0, 0);

  res.x = ap->x + (bp->x - ap->x) * progress;
  res.y = ap->y + (bp->y - ap->y) * progress;

  g_value_set_boxed (retval, &res);

  return TRUE;
}

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterPoint, clutter_point,
                               clutter_point_copy,
                               clutter_point_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_point_progress))



/*
 * ClutterSize
 */

/**
 * clutter_size_alloc:
 *
 * Allocates a new #ClutterSize.
 *
 * Return value: (transfer full): the newly allocated #ClutterSize.
 *   Use clutter_size_free() to free its resources.
 *
 * Since: 1.12
 */
ClutterSize *
clutter_size_alloc (void)
{
  return g_slice_new0 (ClutterSize);
}

/**
 * clutter_size_init:
 * @size: a #ClutterSize
 * @width: the width
 * @height: the height
 *
 * Initializes a #ClutterSize with the given dimensions.
 *
 * Return value: (transfer none): the initialized #ClutterSize
 *
 * Since: 1.12
 */
ClutterSize *
clutter_size_init (ClutterSize *size,
                   float        width,
                   float        height)
{
  g_return_val_if_fail (size != NULL, NULL);

  size->width = width;
  size->height = height;

  return size;
}

/**
 * clutter_size_copy:
 * @size: a #ClutterSize
 *
 * Creates a new #ClutterSize and duplicates @size.
 *
 * Return value: (transfer full): the newly allocated #ClutterSize.
 *   Use clutter_size_free() to free its resources.
 *
 * Since: 1.12
 */
ClutterSize *
clutter_size_copy (const ClutterSize *size)
{
  return g_slice_dup (ClutterSize, size);
}

/**
 * clutter_size_free:
 * @size: a #ClutterSize
 *
 * Frees the resources allocated for @size.
 *
 * Since: 1.12
 */
void
clutter_size_free (ClutterSize *size)
{
  if (size != NULL)
    g_slice_free (ClutterSize, size);
}

/**
 * clutter_size_equals:
 * @a: a #ClutterSize to compare
 * @b: a #ClutterSize to compare
 *
 * Compares two #ClutterSize for equality.
 *
 * Return value: %TRUE if the two #ClutterSize are equal
 *
 * Since: 1.12
 */
gboolean
clutter_size_equals (const ClutterSize *a,
                     const ClutterSize *b)
{
  if (a == b)
    return TRUE;

  if (a == NULL || b == NULL)
    return FALSE;

  return fabsf (a->width - b->width) < FLOAT_EPSILON &&
         fabsf (a->height - b->height) < FLOAT_EPSILON;
}

static gboolean
clutter_size_progress (const GValue *a,
                       const GValue *b,
                       gdouble       progress,
                       GValue       *retval)
{
  const ClutterSize *as = g_value_get_boxed (a);
  const ClutterSize *bs = g_value_get_boxed (b);
  ClutterSize res = CLUTTER_SIZE_INIT (0, 0);

  res.width = as->width + (bs->width - as->width) * progress;
  res.height = as->height + (bs->height - as->height) * progress;

  g_value_set_boxed (retval, &res);

  return TRUE;
}

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterSize, clutter_size,
                               clutter_size_copy,
                               clutter_size_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_size_progress))



/*
 * ClutterRect
 */

static gboolean clutter_rect_progress (const GValue *a,
                                       const GValue *b,
                                       gdouble       progress,
                                       GValue       *res);

G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterRect, clutter_rect,
                               clutter_rect_copy,
                               clutter_rect_free,
                               CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_rect_progress))

static inline void
clutter_rect_normalize_internal (ClutterRect *rect)
{
  if (rect->size.width >= 0.f && rect->size.height >= 0.f)
    return;

  if (rect->size.width < 0.f)
    {
      float size = fabsf (rect->size.width);

      rect->origin.x -= size;
      rect->size.width = size;
    }

  if (rect->size.height < 0.f)
    {
      float size = fabsf (rect->size.height);

      rect->origin.y -= size;
      rect->size.height = size;
    }
}

/**
 * clutter_rect_alloc:
 *
 * Creates a new, empty #ClutterRect.
 *
 * You can use clutter_rect_init() to initialize the returned rectangle,
 * for instance:
 *
 * |[
 *   rect = clutter_rect_init (clutter_rect_alloc (), x, y, width, height);
 * ]|
 *
 * Return value: (transfer full): the newly allocated #ClutterRect.
 *   Use clutter_rect_free() to free its resources
 *
 * Since: 1.12
 */
ClutterRect *
clutter_rect_alloc (void)
{
  return g_slice_new0 (ClutterRect);
}

/**
 * clutter_rect_init:
 * @rect: a #ClutterRect
 * @x: X coordinate of the origin
 * @y: Y coordinate of the origin
 * @width: width of the rectangle
 * @height: height of the rectangle
 *
 * Initializes a #ClutterRect with the given origin and size.
 *
 * Return value: (transfer none): the updated rectangle
 *
 * Since: 1.12
 */
ClutterRect *
clutter_rect_init (ClutterRect *rect,
                   float        x,
                   float        y,
                   float        width,
                   float        height)
{
  g_return_val_if_fail (rect != NULL, NULL);

  rect->origin.x = x;
  rect->origin.y = y;

  rect->size.width = width;
  rect->size.height = height;

  return rect;
}

/**
 * clutter_rect_copy:
 * @rect: a #ClutterRect
 *
 * Copies @rect into a new #ClutterRect instance.
 *
 * Return value: (transfer full): the newly allocate copy of @rect.
 *   Use clutter_rect_free() to free the associated resources
 *
 * Since: 1.12
 */
ClutterRect *
clutter_rect_copy (const ClutterRect *rect)
{
  if (rect != NULL)
    {
      ClutterRect *res;

      res = g_slice_dup (ClutterRect, rect);
      clutter_rect_normalize_internal (res);

      return res;
    }

  return NULL;
}

/**
 * clutter_rect_free:
 * @rect: a #ClutterRect
 *
 * Frees the resources allocated by @rect.
 *
 * Since: 1.12
 */
void
clutter_rect_free (ClutterRect *rect)
{
  if (rect != NULL)
    g_slice_free (ClutterRect, rect);
}

/**
 * clutter_rect_equals:
 * @a: a #ClutterRect
 * @b: a #ClutterRect
 *
 * Checks whether @a and @b are equals.
 *
 * This function will normalize both @a and @b before comparing
 * their origin and size.
 *
 * Return value: %TRUE if the rectangles match in origin and size.
 *
 * Since: 1.12
 */
gboolean
clutter_rect_equals (ClutterRect *a,
                     ClutterRect *b)
{
  if (a == b)
    return TRUE;

  if (a == NULL || b == NULL)
    return FALSE;

  clutter_rect_normalize_internal (a);
  clutter_rect_normalize_internal (b);

  return clutter_point_equals (&a->origin, &b->origin) &&
         clutter_size_equals (&a->size, &b->size);
}

/**
 * clutter_rect_normalize:
 * @rect: a #ClutterRect
 *
 * Normalizes a #ClutterRect.
 *
 * A #ClutterRect is defined by the area covered by its size; this means
 * that a #ClutterRect with #ClutterRect.origin in [ 0, 0 ] and a
 * #ClutterRect.size of [ 10, 10 ] is equivalent to a #ClutterRect with
 * #ClutterRect.origin in [ 10, 10 ] and a #ClutterRect.size of [ -10, -10 ].
 *
 * This function is useful to ensure that a rectangle has positive width
 * and height; it will modify the passed @rect and normalize its size.
 *
 * Since: 1.12
 */
ClutterRect *
clutter_rect_normalize (ClutterRect *rect)
{
  g_return_val_if_fail (rect != NULL, NULL);

  clutter_rect_normalize_internal (rect);

  return rect;
}

/**
 * clutter_rect_get_center:
 * @rect: a #ClutterRect
 * @center: (out caller-allocates): a #ClutterPoint
 *
 * Retrieves the center of @rect, after normalizing the rectangle,
 * and updates @center with the correct coordinates.
 *
 * Since: 1.12
 */
void
clutter_rect_get_center (ClutterRect  *rect,
                         ClutterPoint *center)
{
  g_return_if_fail (rect != NULL);
  g_return_if_fail (center != NULL);

  clutter_rect_normalize_internal (rect);

  center->x = rect->origin.x + (rect->size.width / 2.0f);
  center->y = rect->origin.y + (rect->size.height / 2.0f);
}

/**
 * clutter_rect_contains_point:
 * @rect: a #ClutterRect
 * @point: the point to check
 *
 * Checks whether @point is contained by @rect, after normalizing the
 * rectangle.
 *
 * Return value: %TRUE if the @point is contained by @rect.
 *
 * Since: 1.12
 */
gboolean
clutter_rect_contains_point (ClutterRect  *rect,
                             ClutterPoint *point)
{
  g_return_val_if_fail (rect != NULL, FALSE);
  g_return_val_if_fail (point != NULL, FALSE);

  clutter_rect_normalize_internal (rect);

  return (point->x >= rect->origin.x) &&
         (point->y >= rect->origin.y) &&
         (point->x <= (rect->origin.x + rect->size.width)) &&
         (point->y <= (rect->origin.y + rect->size.height));
}

/**
 * clutter_rect_contains_rect:
 * @a: a #ClutterRect
 * @b: a #ClutterRect
 *
 * Checks whether @a contains @b.
 *
 * The first rectangle contains the second if the union of the
 * two #ClutterRect is equal to the first rectangle.
 *
 * Return value: %TRUE if the first rectangle contains the second.
 *
 * Since: 1.12
 */
gboolean
clutter_rect_contains_rect (ClutterRect *a,
                            ClutterRect *b)
{
  ClutterRect res;

  g_return_val_if_fail (a != NULL, FALSE);
  g_return_val_if_fail (b != NULL, FALSE);

  clutter_rect_union (a, b, &res);

  return clutter_rect_equals (a, &res);
}

/**
 * clutter_rect_union:
 * @a: a #ClutterRect
 * @b: a #ClutterRect
 * @res: (out caller-allocates): a #ClutterRect
 *
 * Computes the smallest possible rectangle capable of fully containing
 * both @a and @b, and places it into @res.
 *
 * This function will normalize both @a and @b prior to computing their
 * union.
 *
 * Since: 1.12
 */
void
clutter_rect_union (ClutterRect *a,
                    ClutterRect *b,
                    ClutterRect *res)
{
  g_return_if_fail (a != NULL);
  g_return_if_fail (b != NULL);
  g_return_if_fail (res != NULL);

  clutter_rect_normalize_internal (a);
  clutter_rect_normalize_internal (b);

  res->origin.x = MIN (a->origin.x, b->origin.x);
  res->origin.y = MIN (a->origin.y, b->origin.y);

  res->size.width = MAX (a->size.width, b->size.width);
  res->size.height = MAX (a->size.height, b->size.height);
}

/**
 * clutter_rect_intersection:
 * @a: a #ClutterRect
 * @b: a #ClutterRect
 * @res: (out caller-allocates) (allow-none): a #ClutterRect, or %NULL
 *
 * Computes the intersection of @a and @b, and places it in @res, if @res
 * is not %NULL.
 *
 * This function will normalize both @a and @b prior to computing their
 * intersection.
 *
 * This function can be used to simply check if the intersection of @a and @b
 * is not empty, by using %NULL for @res.
 *
 * Return value: %TRUE if the intersection of @a and @b is not empty
 *
 * Since: 1.12
 */
gboolean
clutter_rect_intersection (ClutterRect *a,
                           ClutterRect *b,
                           ClutterRect *res)
{
  float x_1, y_1, x_2, y_2;

  g_return_val_if_fail (a != NULL, FALSE);
  g_return_val_if_fail (b != NULL, FALSE);

  clutter_rect_normalize_internal (a);
  clutter_rect_normalize_internal (b);

  x_1 = MAX (a->origin.x, b->origin.y);
  y_1 = MAX (a->origin.y, b->origin.y);
  x_2 = MIN (a->origin.x + a->size.width, b->origin.x + b->size.width);
  y_2 = MIN (a->origin.y + a->size.height, b->origin.y + b->size.height);

  if (x_1 >= x_2 || y_1 >= y_2)
    {
      if (res != NULL)
        clutter_rect_init (res, 0.f, 0.f, 0.f, 0.f);

      return FALSE;
    }

  if (res != NULL)
    clutter_rect_init (res, x_1, y_1, x_2 - x_1, y_2 - y_1);

  return TRUE;
}

/**
 * clutter_rect_offset:
 * @rect: a #ClutterRect
 * @d_x: the horizontal offset value
 * @d_y: the vertical offset value
 *
 * Offsets the origin of @rect by the given values, after normalizing
 * the rectangle.
 *
 * Since: 1.12
 */
void
clutter_rect_offset (ClutterRect *rect,
                     float        d_x,
                     float        d_y)
{
  g_return_if_fail (rect != NULL);

  clutter_rect_normalize_internal (rect);

  rect->origin.x += d_x;
  rect->origin.y += d_y;
}

/**
 * clutter_rect_inset:
 * @rect: a #ClutterRect
 * @d_x: an horizontal value; a positive @d_x will create an inset rectangle,
 *   and a negative value will create a larger rectangle
 * @d_y: a vertical value; a positive @d_x will create an inset rectangle,
 *   and a negative value will create a larger rectangle
 *
 * Normalizes the @rect and offsets its origin by the @d_x and @d_y values;
 * the size is adjusted by (2 * @d_x, 2 * @d_y).
 *
 * If @d_x and @d_y are positive the size of the rectangle is decreased; if
 * the values are negative, the size of the rectangle is increased.
 *
 * If the resulting rectangle has a negative width or height, the size is
 * set to 0.
 *
 * Since: 1.12
 */
void
clutter_rect_inset (ClutterRect *rect,
                    float        d_x,
                    float        d_y)
{
  g_return_if_fail (rect != NULL);

  clutter_rect_normalize_internal (rect);

  rect->origin.x += d_x;
  rect->origin.y += d_y;

  if (d_x >= 0.f)
    rect->size.width -= (d_x * 2.f);
  else
    rect->size.width += (d_x * -2.f);

  if (d_y >= 0.f)
    rect->size.height -= (d_y * 2.f);
  else
    rect->size.height += (d_y * -2.f);

  if (rect->size.width < 0.f)
    rect->size.width = 0.f;

  if (rect->size.height < 0.f)
    rect->size.height = 0.f;
}

/**
 * clutter_rect_clamp_to_pixel:
 * @rect: a #ClutterRect
 *
 * Rounds the origin of @rect downwards to the nearest integer, and rounds
 * the size of @rect upwards to the nearest integer, so that @rect is
 * updated to the smallest rectangle capable of fully containing the
 * original, fractional rectangle.
 *
 * Since: 1.12
 */
void
clutter_rect_clamp_to_pixel (ClutterRect *rect)
{
  g_return_if_fail (rect != NULL);

  clutter_rect_normalize_internal (rect);

  rect->origin.x = floorf (rect->origin.x);
  rect->origin.y = floorf (rect->origin.y);

  rect->size.width = ceilf (rect->size.width);
  rect->size.height = ceilf (rect->size.height);
}

/**
 * clutter_rect_get_x:
 * @rect: a #ClutterRect
 *
 * Retrieves the X coordinate of the origin of @rect.
 *
 * Return value: the X coordinate of the origin of the rectangle
 *
 * Since: 1.12
 */
float
clutter_rect_get_x (ClutterRect *rect)
{
  g_return_val_if_fail (rect != NULL, 0.f);

  clutter_rect_normalize_internal (rect);

  return rect->origin.x;
}

/**
 * clutter_rect_get_y:
 * @rect: a #ClutterRect
 *
 * Retrieves the Y coordinate of the origin of @rect.
 *
 * Return value: the Y coordinate of the origin of the rectangle
 *
 * Since: 1.12
 */
float
clutter_rect_get_y (ClutterRect *rect)
{
  g_return_val_if_fail (rect != NULL, 0.f);

  clutter_rect_normalize_internal (rect);

  return rect->origin.y;
}

/**
 * clutter_rect_get_width:
 * @rect: a #ClutterRect
 *
 * Retrieves the width of @rect.
 *
 * Return value: the width of the rectangle
 *
 * Since: 1.12
 */
float
clutter_rect_get_width (ClutterRect *rect)
{
  g_return_val_if_fail (rect != NULL, 0.f);

  clutter_rect_normalize_internal (rect);

  return rect->size.width;
}

/**
 * clutter_rect_get_height:
 * @rect: a #ClutterRect
 *
 * Retrieves the height of @rect.
 *
 * Return value: the height of the rectangle
 *
 * Since: 1.12
 */
float
clutter_rect_get_height (ClutterRect *rect)
{
  g_return_val_if_fail (rect != NULL, 0.f);

  clutter_rect_normalize_internal (rect);

  return rect->size.height;
}

static gboolean
clutter_rect_progress (const GValue *a,
                       const GValue *b,
                       gdouble       progress,
                       GValue       *retval)
{
  const ClutterRect *rect_a = g_value_get_boxed (a);
  const ClutterRect *rect_b = g_value_get_boxed (b);
  ClutterRect res = CLUTTER_RECT_INIT_ZERO;

#define INTERPOLATE(r_a,r_b,member,field,factor)     ((r_a)->member.field + (((r_b)->member.field - ((r_b)->member.field)) * (factor)))

  res.origin.x = INTERPOLATE (rect_a, rect_b, origin, x, progress);
  res.origin.y = INTERPOLATE (rect_a, rect_b, origin, y, progress);

  res.size.width = INTERPOLATE (rect_a, rect_b, size, width, progress);
  res.size.height = INTERPOLATE (rect_a, rect_b, size, height, progress);

#undef INTERPOLATE

  clutter_rect_normalize_internal (&res);

  g_value_set_boxed (retval, &res);

  return TRUE;
}