/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <glib-object.h>

#include "libgimpmath/gimpmath.h"

#include "core-types.h"

#include "gimp-transform-utils.h"
#include "gimpcoords.h"
#include "gimpcoords-interpolate.h"


#define EPSILON 1e-6


void
gimp_transform_get_rotate_center (gint      x,
                                  gint      y,
                                  gint      width,
                                  gint      height,
                                  gboolean  auto_center,
                                  gdouble  *center_x,
                                  gdouble  *center_y)
{
  g_return_if_fail (center_x != NULL);
  g_return_if_fail (center_y != NULL);

  if (auto_center)
    {
      *center_x = (gdouble) x + (gdouble) width  / 2.0;
      *center_y = (gdouble) y + (gdouble) height / 2.0;
    }
}

void
gimp_transform_get_flip_axis (gint                 x,
                              gint                 y,
                              gint                 width,
                              gint                 height,
                              GimpOrientationType  flip_type,
                              gboolean             auto_center,
                              gdouble             *axis)
{
  g_return_if_fail (axis != NULL);

  if (auto_center)
    {
      switch (flip_type)
        {
        case GIMP_ORIENTATION_HORIZONTAL:
          *axis = ((gdouble) x + (gdouble) width / 2.0);
          break;

        case GIMP_ORIENTATION_VERTICAL:
          *axis = ((gdouble) y + (gdouble) height / 2.0);
          break;

        default:
          g_return_if_reached ();
          break;
        }
    }
}

void
gimp_transform_matrix_flip (GimpMatrix3         *matrix,
                            GimpOrientationType  flip_type,
                            gdouble              axis)
{
  g_return_if_fail (matrix != NULL);

  switch (flip_type)
    {
    case GIMP_ORIENTATION_HORIZONTAL:
      gimp_matrix3_translate (matrix, - axis, 0.0);
      gimp_matrix3_scale (matrix, -1.0, 1.0);
      gimp_matrix3_translate (matrix, axis, 0.0);
      break;

    case GIMP_ORIENTATION_VERTICAL:
      gimp_matrix3_translate (matrix, 0.0, - axis);
      gimp_matrix3_scale (matrix, 1.0, -1.0);
      gimp_matrix3_translate (matrix, 0.0, axis);
      break;

    case GIMP_ORIENTATION_UNKNOWN:
      break;
    }
}

void
gimp_transform_matrix_flip_free (GimpMatrix3 *matrix,
                                 gdouble      x1,
                                 gdouble      y1,
                                 gdouble      x2,
                                 gdouble      y2)
{
  gdouble angle;

  g_return_if_fail (matrix != NULL);

  angle = atan2  (y2 - y1, x2 - x1);

  gimp_matrix3_identity  (matrix);
  gimp_matrix3_translate (matrix, -x1, -y1);
  gimp_matrix3_rotate    (matrix, -angle);
  gimp_matrix3_scale     (matrix, 1.0, -1.0);
  gimp_matrix3_rotate    (matrix, angle);
  gimp_matrix3_translate (matrix, x1, y1);
}

void
gimp_transform_matrix_rotate (GimpMatrix3         *matrix,
                              GimpRotationType     rotate_type,
                              gdouble              center_x,
                              gdouble              center_y)
{
  gdouble angle = 0;

  switch (rotate_type)
    {
    case GIMP_ROTATE_90:
      angle = G_PI_2;
      break;
    case GIMP_ROTATE_180:
      angle = G_PI;
      break;
    case GIMP_ROTATE_270:
      angle = - G_PI_2;
      break;
    }

  gimp_transform_matrix_rotate_center (matrix, center_x, center_y, angle);
}

void
gimp_transform_matrix_rotate_rect (GimpMatrix3 *matrix,
                                   gint         x,
                                   gint         y,
                                   gint         width,
                                   gint         height,
                                   gdouble      angle)
{
  gdouble center_x;
  gdouble center_y;

  g_return_if_fail (matrix != NULL);

  center_x = (gdouble) x + (gdouble) width  / 2.0;
  center_y = (gdouble) y + (gdouble) height / 2.0;

  gimp_matrix3_translate (matrix, -center_x, -center_y);
  gimp_matrix3_rotate    (matrix, angle);
  gimp_matrix3_translate (matrix, +center_x, +center_y);
}

void
gimp_transform_matrix_rotate_center (GimpMatrix3 *matrix,
                                     gdouble      center_x,
                                     gdouble      center_y,
                                     gdouble      angle)
{
  g_return_if_fail (matrix != NULL);

  gimp_matrix3_translate (matrix, -center_x, -center_y);
  gimp_matrix3_rotate    (matrix, angle);
  gimp_matrix3_translate (matrix, +center_x, +center_y);
}

void
gimp_transform_matrix_scale (GimpMatrix3 *matrix,
                             gint         x,
                             gint         y,
                             gint         width,
                             gint         height,
                             gdouble      t_x,
                             gdouble      t_y,
                             gdouble      t_width,
                             gdouble      t_height)
{
  gdouble scale_x = 1.0;
  gdouble scale_y = 1.0;

  g_return_if_fail (matrix != NULL);

  if (width > 0)
    scale_x = t_width / (gdouble) width;

  if (height > 0)
    scale_y = t_height / (gdouble) height;

  gimp_matrix3_identity  (matrix);
  gimp_matrix3_translate (matrix, -x, -y);
  gimp_matrix3_scale     (matrix, scale_x, scale_y);
  gimp_matrix3_translate (matrix, t_x, t_y);
}

void
gimp_transform_matrix_shear (GimpMatrix3         *matrix,
                             gint                 x,
                             gint                 y,
                             gint                 width,
                             gint                 height,
                             GimpOrientationType  orientation,
                             gdouble              amount)
{
  gdouble center_x;
  gdouble center_y;

  g_return_if_fail (matrix != NULL);

  if (width == 0)
    width = 1;

  if (height == 0)
    height = 1;

  center_x = (gdouble) x + (gdouble) width  / 2.0;
  center_y = (gdouble) y + (gdouble) height / 2.0;

  gimp_matrix3_identity  (matrix);
  gimp_matrix3_translate (matrix, -center_x, -center_y);

  if (orientation == GIMP_ORIENTATION_HORIZONTAL)
    gimp_matrix3_xshear (matrix, amount / height);
  else
    gimp_matrix3_yshear (matrix, amount / width);

  gimp_matrix3_translate (matrix, +center_x, +center_y);
}

void
gimp_transform_matrix_perspective (GimpMatrix3 *matrix,
                                   gint         x,
                                   gint         y,
                                   gint         width,
                                   gint         height,
                                   gdouble      t_x1,
                                   gdouble      t_y1,
                                   gdouble      t_x2,
                                   gdouble      t_y2,
                                   gdouble      t_x3,
                                   gdouble      t_y3,
                                   gdouble      t_x4,
                                   gdouble      t_y4)
{
  GimpMatrix3 trafo;
  gdouble     scalex;
  gdouble     scaley;

  g_return_if_fail (matrix != NULL);

  scalex = scaley = 1.0;

  if (width > 0)
    scalex = 1.0 / (gdouble) width;

  if (height > 0)
    scaley = 1.0 / (gdouble) height;

  gimp_matrix3_translate (matrix, -x, -y);
  gimp_matrix3_scale     (matrix, scalex, scaley);

  /* Determine the perspective transform that maps from
   * the unit cube to the transformed coordinates
   */
  {
    gdouble dx1, dx2, dx3, dy1, dy2, dy3;

    dx1 = t_x2 - t_x4;
    dx2 = t_x3 - t_x4;
    dx3 = t_x1 - t_x2 + t_x4 - t_x3;

    dy1 = t_y2 - t_y4;
    dy2 = t_y3 - t_y4;
    dy3 = t_y1 - t_y2 + t_y4 - t_y3;

    /*  Is the mapping affine?  */
    if ((dx3 == 0.0) && (dy3 == 0.0))
      {
        trafo.coeff[0][0] = t_x2 - t_x1;
        trafo.coeff[0][1] = t_x4 - t_x2;
        trafo.coeff[0][2] = t_x1;
        trafo.coeff[1][0] = t_y2 - t_y1;
        trafo.coeff[1][1] = t_y4 - t_y2;
        trafo.coeff[1][2] = t_y1;
        trafo.coeff[2][0] = 0.0;
        trafo.coeff[2][1] = 0.0;
      }
    else
      {
        gdouble det1, det2;

        det1 = dx3 * dy2 - dy3 * dx2;
        det2 = dx1 * dy2 - dy1 * dx2;

        trafo.coeff[2][0] = (det2 == 0.0) ? 1.0 : det1 / det2;

        det1 = dx1 * dy3 - dy1 * dx3;

        trafo.coeff[2][1] = (det2 == 0.0) ? 1.0 : det1 / det2;

        trafo.coeff[0][0] = t_x2 - t_x1 + trafo.coeff[2][0] * t_x2;
        trafo.coeff[0][1] = t_x3 - t_x1 + trafo.coeff[2][1] * t_x3;
        trafo.coeff[0][2] = t_x1;

        trafo.coeff[1][0] = t_y2 - t_y1 + trafo.coeff[2][0] * t_y2;
        trafo.coeff[1][1] = t_y3 - t_y1 + trafo.coeff[2][1] * t_y3;
        trafo.coeff[1][2] = t_y1;
      }

    trafo.coeff[2][2] = 1.0;
  }

  gimp_matrix3_mult (&trafo, matrix);
}

/* modified gaussian algorithm
 * solves a system of linear equations
 *
 * Example:
 * 1x + 2y + 4z = 25
 * 2x + 1y      = 4
 * 3x + 5y + 2z = 23
 * Solution: x=1, y=2, z=5
 *
 * Input:
 * matrix = { 1,2,4,25,2,1,0,4,3,5,2,23 }
 * s = 3 (Number of variables)
 * Output:
 * return value == TRUE (TRUE, if there is a single unique solution)
 * solution == { 1,2,5 } (if the return value is FALSE, the content
 * of solution is of no use)
 */
static gboolean
mod_gauss (gdouble matrix[],
           gdouble solution[],
           gint    s)
{
  gint    p[s]; /* row permutation */
  gint    i, j, r, temp;
  gdouble q;
  gint    t = s + 1;

  for (i = 0; i < s; i++)
    {
      p[i] = i;
    }

  for (r = 0; r < s; r++)
    {
      /* make sure that (r,r) is not 0 */
      if (fabs (matrix[p[r] * t + r]) <= EPSILON)
        {
          /* we need to permutate rows */
          for (i = r + 1; i <= s; i++)
            {
              if (i == s)
                {
                  /* if this happens, the linear system has zero or
                   * more than one solutions.
                   */
                  return FALSE;
                }

              if (fabs (matrix[p[i] * t + r]) > EPSILON)
                break;
            }

          temp = p[r];
          p[r] = p[i];
          p[i] = temp;
        }

      /* make (r,r) == 1 */
      q = 1.0 / matrix[p[r] * t + r];
      matrix[p[r] * t + r] = 1.0;

      for (j = r + 1; j < t; j++)
        {
          matrix[p[r] * t + j] *= q;
        }

      /* make that all entries in column r are 0 (except (r,r)) */
      for (i = 0; i < s; i++)
        {
          if (i == r)
            continue;

          for (j = r + 1; j < t ; j++)
            {
              matrix[p[i] * t + j] -= matrix[p[r] * t + j] * matrix[p[i] * t + r];
            }

          /* we don't need to execute the following line
           * since we won't access this element again:
           *
           * matrix[p[i] * t + r] = 0.0;
           */
        }
    }

  for (i = 0; i < s; i++)
    {
      solution[i] = matrix[p[i] * t + s];
    }

  return TRUE;
}

/* multiplies 'matrix' by the matrix that transforms a set of 4 'input_points'
 * to corresponding 'output_points', if such matrix exists, and is valid (i.e.,
 * keeps the output points in front of the camera).
 *
 * returns TRUE if successful.
 */
gboolean
gimp_transform_matrix_generic (GimpMatrix3       *matrix,
                               const GimpVector2  input_points[4],
                               const GimpVector2  output_points[4])
{
  GimpMatrix3 trafo;
  gdouble     coeff[8 * 9];
  gboolean    negative = -1;
  gint        i;
  gboolean    result   = TRUE;

  g_return_val_if_fail (matrix != NULL, FALSE);
  g_return_val_if_fail (input_points != NULL, FALSE);
  g_return_val_if_fail (output_points != NULL, FALSE);

  /* find the matrix that transforms 'input_points' to 'output_points', whose
   * (3, 3) coeffcient is 1, by solving a system of linear equations whose
   * solution is the remaining 8 coefficients.
   */
  for (i = 0; i < 4; i++)
    {
      coeff[i * 9 + 0] = input_points[i].x;
      coeff[i * 9 + 1] = input_points[i].y;
      coeff[i * 9 + 2] = 1.0;
      coeff[i * 9 + 3] = 0.0;
      coeff[i * 9 + 4] = 0.0;
      coeff[i * 9 + 5] = 0.0;
      coeff[i * 9 + 6] = -input_points[i].x * output_points[i].x;
      coeff[i * 9 + 7] = -input_points[i].y * output_points[i].x;
      coeff[i * 9 + 8] =                      output_points[i].x;

      coeff[(i + 4) * 9 + 0] = 0.0;
      coeff[(i + 4) * 9 + 1] = 0.0;
      coeff[(i + 4) * 9 + 2] = 0.0;
      coeff[(i + 4) * 9 + 3] = input_points[i].x;
      coeff[(i + 4) * 9 + 4] = input_points[i].y;
      coeff[(i + 4) * 9 + 5] = 1.0;
      coeff[(i + 4) * 9 + 6] = -input_points[i].x * output_points[i].y;
      coeff[(i + 4) * 9 + 7] = -input_points[i].y * output_points[i].y;
      coeff[(i + 4) * 9 + 8] =                      output_points[i].y;
    }

  /* if there is no solution, bail */
  if (! mod_gauss (coeff, (gdouble *) trafo.coeff, 8))
    return FALSE;

  trafo.coeff[2][2] = 1.0;

  /* make sure that none of the input points maps to a point at infinity, and
   * that all output points are on the same side of the camera.
   */
  for (i = 0; i < 4; i++)
    {
      gdouble  w;
      gboolean neg;

      w = trafo.coeff[2][0] * input_points[i].x +
          trafo.coeff[2][1] * input_points[i].y +
          trafo.coeff[2][2];

      if (fabs (w) <= EPSILON)
        result = FALSE;

      neg = (w < 0.0);

      if (negative < 0)
        {
          negative = neg;
        }
      else if (neg != negative)
        {
          result = FALSE;
          break;
        }
    }

  /* if the output points are all behind the camera, negate the matrix, which
   * would map the input points to the corresponding points in front of the
   * camera.
   */
  if (negative > 0)
    {
      gint r;
      gint c;

      for (r = 0; r < 3; r++)
        {
          for (c = 0; c < 3; c++)
            {
              trafo.coeff[r][c] = -trafo.coeff[r][c];
            }
        }
    }

  /* append the transformation to 'matrix' */
  gimp_matrix3_mult (&trafo, matrix);

  return result;
}

gboolean
gimp_transform_polygon_is_convex (gdouble x1,
                                  gdouble y1,
                                  gdouble x2,
                                  gdouble y2,
                                  gdouble x3,
                                  gdouble y3,
                                  gdouble x4,
                                  gdouble y4)
{
  gdouble z1, z2, z3, z4;

  /* We test if the transformed polygon is convex.  if z1 and z2 have
   * the same sign as well as z3 and z4 the polygon is convex.
   */
  z1 = ((x2 - x1) * (y4 - y1) -
        (x4 - x1) * (y2 - y1));
  z2 = ((x4 - x1) * (y3 - y1) -
        (x3 - x1) * (y4 - y1));
  z3 = ((x4 - x2) * (y3 - y2) -
        (x3 - x2) * (y4 - y2));
  z4 = ((x3 - x2) * (y1 - y2) -
        (x1 - x2) * (y3 - y2));

  return (z1 * z2 > 0) && (z3 * z4 > 0);
}

/* transforms the polygon or polyline, whose vertices are given by 'vertices',
 * by 'matrix', performing clipping by the near plane.  'closed' indicates
 * whether the vertices represent a polygon ('closed == TRUE') or a polyline
 * ('closed == FALSE').
 *
 * returns the transformed vertices in 't_vertices', and their count in
 * 'n_t_vertices'.  the minimal possible number of transformed vertices is 0,
 * which happens when the entire input is clipped.  in general, the maximal
 * possible number of transformed vertices is '3 * n_vertices / 2' (rounded
 * down), however, for convex polygons the number is 'n_vertices + 1', and for
 * a single line segment ('n_vertices == 2' and 'closed == FALSE') the number
 * is 2.
 *
 * 't_vertices' may not alias 'vertices', except when transforming a single
 * line segment.
 */
void
gimp_transform_polygon (const GimpMatrix3 *matrix,
                        const GimpVector2 *vertices,
                        gint               n_vertices,
                        gboolean           closed,
                        GimpVector2       *t_vertices,
                        gint              *n_t_vertices)
{
  GimpVector3 curr;
  gboolean    curr_visible;
  gint        i;

  g_return_if_fail (matrix != NULL);
  g_return_if_fail (vertices != NULL);
  g_return_if_fail (n_vertices >= 0);
  g_return_if_fail (t_vertices != NULL);
  g_return_if_fail (n_t_vertices != NULL);

  *n_t_vertices = 0;

  if (n_vertices == 0)
    return;

  curr.x = matrix->coeff[0][0] * vertices[0].x +
           matrix->coeff[0][1] * vertices[0].y +
           matrix->coeff[0][2];
  curr.y = matrix->coeff[1][0] * vertices[0].x +
           matrix->coeff[1][1] * vertices[0].y +
           matrix->coeff[1][2];
  curr.z = matrix->coeff[2][0] * vertices[0].x +
           matrix->coeff[2][1] * vertices[0].y +
           matrix->coeff[2][2];

  curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z);

  for (i = 0; i < n_vertices; i++)
    {
      if (curr_visible)
        {
          t_vertices[(*n_t_vertices)++] = (GimpVector2) { curr.x / curr.z,
                                                          curr.y / curr.z };
        }

      if (i < n_vertices - 1 || closed)
        {
          GimpVector3 next;
          gboolean    next_visible;
          gint        j = (i + 1) % n_vertices;

          next.x = matrix->coeff[0][0] * vertices[j].x +
                   matrix->coeff[0][1] * vertices[j].y +
                   matrix->coeff[0][2];
          next.y = matrix->coeff[1][0] * vertices[j].x +
                   matrix->coeff[1][1] * vertices[j].y +
                   matrix->coeff[1][2];
          next.z = matrix->coeff[2][0] * vertices[j].x +
                   matrix->coeff[2][1] * vertices[j].y +
                   matrix->coeff[2][2];

          next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z);

          if (next_visible != curr_visible)
            {
              gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z);

              t_vertices[(*n_t_vertices)++] =
                (GimpVector2) { (curr.x + (next.x - curr.x) * ratio) / GIMP_TRANSFORM_NEAR_Z,
                                (curr.y + (next.y - curr.y) * ratio) / GIMP_TRANSFORM_NEAR_Z };
            }

          curr         = next;
          curr_visible = next_visible;
        }
    }
}

/* same as gimp_transform_polygon(), but using GimpCoords as the vertex type,
 * instead of GimpVector2.
 */
void
gimp_transform_polygon_coords (const GimpMatrix3 *matrix,
                               const GimpCoords  *vertices,
                               gint               n_vertices,
                               gboolean           closed,
                               GimpCoords        *t_vertices,
                               gint              *n_t_vertices)
{
  GimpVector3 curr;
  gboolean    curr_visible;
  gint        i;

  g_return_if_fail (matrix != NULL);
  g_return_if_fail (vertices != NULL);
  g_return_if_fail (n_vertices >= 0);
  g_return_if_fail (t_vertices != NULL);
  g_return_if_fail (n_t_vertices != NULL);

  *n_t_vertices = 0;

  if (n_vertices == 0)
    return;

  curr.x = matrix->coeff[0][0] * vertices[0].x +
           matrix->coeff[0][1] * vertices[0].y +
           matrix->coeff[0][2];
  curr.y = matrix->coeff[1][0] * vertices[0].x +
           matrix->coeff[1][1] * vertices[0].y +
           matrix->coeff[1][2];
  curr.z = matrix->coeff[2][0] * vertices[0].x +
           matrix->coeff[2][1] * vertices[0].y +
           matrix->coeff[2][2];

  curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z);

  for (i = 0; i < n_vertices; i++)
    {
      if (curr_visible)
        {
          t_vertices[*n_t_vertices]   = vertices[i];
          t_vertices[*n_t_vertices].x = curr.x / curr.z;
          t_vertices[*n_t_vertices].y = curr.y / curr.z;

          (*n_t_vertices)++;
        }

      if (i < n_vertices - 1 || closed)
        {
          GimpVector3 next;
          gboolean    next_visible;
          gint        j = (i + 1) % n_vertices;

          next.x = matrix->coeff[0][0] * vertices[j].x +
                   matrix->coeff[0][1] * vertices[j].y +
                   matrix->coeff[0][2];
          next.y = matrix->coeff[1][0] * vertices[j].x +
                   matrix->coeff[1][1] * vertices[j].y +
                   matrix->coeff[1][2];
          next.z = matrix->coeff[2][0] * vertices[j].x +
                   matrix->coeff[2][1] * vertices[j].y +
                   matrix->coeff[2][2];

          next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z);

          if (next_visible != curr_visible)
            {
              gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z);

              gimp_coords_mix (1.0 - ratio, &vertices[i],
                                     ratio, &vertices[j],
                                            &t_vertices[*n_t_vertices]);

              t_vertices[*n_t_vertices].x = (curr.x + (next.x - curr.x) * ratio) /
                                             GIMP_TRANSFORM_NEAR_Z;
              t_vertices[*n_t_vertices].y = (curr.y + (next.y - curr.y) * ratio) /
                                             GIMP_TRANSFORM_NEAR_Z;

              (*n_t_vertices)++;
            }

          curr         = next;
          curr_visible = next_visible;
        }
    }
}

/* returns the value of the polynomial 'poly', of degree 'degree', at 'x'.  the
 * coefficients of 'poly' should be specified in descending-degree order.
 */
static gdouble
polynomial_eval (const gdouble *poly,
                 gint           degree,
                 gdouble        x)
{
  gdouble y = poly[0];
  gint    i;

  for (i = 1; i <= degree; i++)
    y = y * x + poly[i];

  return y;
}

/* derives the polynomial 'poly', of degree 'degree'.
 *
 * returns the derivative in 'result'.
 */
static void
polynomial_derive (const gdouble *poly,
                   gint           degree,
                   gdouble       *result)
{
  while (degree)
    *result++ = *poly++ * degree--;
}

/* finds the real odd-multiplicity root of the polynomial 'poly', of degree
 * 'degree', inside the range '(x1, x2)'.
 *
 * returns TRUE if such a root exists, and stores its value in '*root'.
 *
 * 'poly' shall be monotonic in the range '(x1, x2)'.
 */
static gboolean
polynomial_odd_root (const gdouble *poly,
                     gint           degree,
                     gdouble        x1,
                     gdouble        x2,
                     gdouble       *root)
{
  gdouble y1;
  gdouble y2;
  gint    i;

  y1 = polynomial_eval (poly, degree, x1);
  y2 = polynomial_eval (poly, degree, x2);

  if (y1 * y2 > -EPSILON)
    {
      /* the two endpoints have the same sign, or one of them is zero.  there's
       * no root inside the range.
       */
      return FALSE;
    }
  else if (y1 > 0.0)
    {
      gdouble t;

      /* if the first endpoint is positive, swap the endpoints, so that the
       * first endpoint is always negative, and the second endpoint is always
       * positive.
       */

      t  = x1;
      x1 = x2;
      x2 = t;
    }

  /* approximate the root using binary search */
  for (i = 0; i < 53; i++)
    {
      gdouble x = (x1 + x2) / 2.0;
      gdouble y = polynomial_eval (poly, degree, x);

      if (y > 0.0)
        x2 = x;
      else
        x1 = x;
    }

  *root = (x1 + x2) / 2.0;

  return TRUE;
}

/* finds the real odd-multiplicity roots of the polynomial 'poly', of degree
 * 'degree', inside the range '(x1, x2)'.
 *
 * returns the roots in 'roots', in ascending order, and their count in
 * 'n_roots'.
 */
static void
polynomial_odd_roots (const gdouble *poly,
                      gint           degree,
                      gdouble        x1,
                      gdouble        x2,
                      gdouble       *roots,
                      gint          *n_roots)
{
  *n_roots = 0;

  /* find the real degree of the polynomial (skip any leading coefficients that
   * are 0)
   */
  for (; degree && fabs (*poly) < EPSILON; poly++, degree--);

  #define ADD_ROOT(root) \
    do \
      { \
        gdouble r = (root); \
        \
        if (r > x1 && r < x2) \
          roots[(*n_roots)++] = r; \
      } \
    while (FALSE)

  switch (degree)
    {
    /* constant case */
    case 0:
      break;

    /* linear case */
    case 1:
      ADD_ROOT (-poly[1] / poly[0]);
      break;

    /* quadratic case */
    case 2:
      {
        gdouble s = SQR (poly[1]) - 4 * poly[0] * poly[2];

        if (s > EPSILON)
          {
            s = sqrt (s);

            if (poly[0] < 0.0)
              s = -s;

            ADD_ROOT ((-poly[1] - s) / (2.0 * poly[0]));
            ADD_ROOT ((-poly[1] + s) / (2.0 * poly[0]));
          }

        break;
      }

    /* general case */
    default:
      {
        gdouble deriv[degree];
        gdouble deriv_roots[degree - 1];
        gint    n_deriv_roots;
        gdouble a;
        gdouble b;
        gint    i;

        /* find the odd roots of the derivative, i.e., the local extrema of the
         * polynomial
         */
        polynomial_derive (poly, degree, deriv);
        polynomial_odd_roots (deriv, degree - 1, x1, x2,
                              deriv_roots, &n_deriv_roots);

        /* search for roots between each consecutive pair of extrema, including
         * the endpoints
         */
        a = x1;

        for (i = 0; i <= n_deriv_roots; i++)
          {
            if (i < n_deriv_roots)
              b = deriv_roots[i];
            else
              b = x2;

            *n_roots += polynomial_odd_root (poly, degree, a, b,
                                             &roots[*n_roots]);

            a = b;
          }

        break;
      }
    }

  #undef ADD_ROOT
}

/* clips the cubic bezier segment, defined by the four control points 'bezier',
 * to the halfplane 'ax + by + c >= 0'.
 *
 * returns the clipped set of bezier segments in 'c_bezier', and their count in
 * 'n_c_bezier'.  the minimal possible number of clipped segments is 0, which
 * happens when the entire segment is clipped.  the maximal possible number of
 * clipped segments is 2.
 *
 * if the first clipped segment is an initial segment of 'bezier', sets
 * '*start_in' to TRUE, otherwise to FALSE.  if the last clipped segment is a
 * final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE.
 *
 * 'c_bezier' may not alias 'bezier'.
 */
static void
clip_bezier (const GimpCoords  bezier[4],
             gdouble           a,
             gdouble           b,
             gdouble           c,
             GimpCoords        c_bezier[2][4],
             gint             *n_c_bezier,
             gboolean         *start_in,
             gboolean         *end_in)
{
  gdouble dot[4];
  gdouble poly[4];
  gdouble roots[5];
  gint    n_roots;
  gint    n_positive;
  gint    i;

  n_positive = 0;

  for (i = 0; i < 4; i++)
    {
      dot[i] = a * bezier[i].x + b * bezier[i].y + c;

      n_positive += (dot[i] >= 0.0);
    }

  if (n_positive == 0)
    {
      /* all points are out -- the entire segment is out */

      *n_c_bezier = 0;
      *start_in   = FALSE;
      *end_in     = FALSE;

      return;
    }
  else if (n_positive == 4)
    {
      /* all points are in -- the entire segment is in */

      memcpy (c_bezier[0], bezier, sizeof (GimpCoords[4]));

      *n_c_bezier = 1;
      *start_in   = TRUE;
      *end_in     = TRUE;

      return;
    }

  /* find the points of intersection of the segment with the 'ax + by + c = 0'
   * line
   */
  poly[0] = dot[3] - 3.0 * dot[2] + 3.0 * dot[1] - dot[0];
  poly[1] = 3.0 * (dot[2] - 2.0 * dot[1] + dot[0]);
  poly[2] = 3.0 * (dot[1] - dot[0]);
  poly[3] = dot[0];

  roots[0] = 0.0;
  polynomial_odd_roots (poly, 3, 0.0, 1.0, roots + 1, &n_roots);
  roots[++n_roots] = 1.0;

  /* construct the list of segments that are inside the halfplane */
  *n_c_bezier = 0;
  *start_in   = (polynomial_eval (poly, 3, roots[1] / 2.0) > 0.0);
  *end_in     = (*start_in + n_roots + 1) % 2;

  for (i = ! *start_in; i < n_roots; i += 2)
    {
      gdouble t0 = roots[i];
      gdouble t1 = roots[i + 1];

      gimp_coords_interpolate_bezier_at (bezier, t0,
                                         &c_bezier[*n_c_bezier][0],
                                         &c_bezier[*n_c_bezier][1]);
      gimp_coords_interpolate_bezier_at (bezier, t1,
                                         &c_bezier[*n_c_bezier][3],
                                         &c_bezier[*n_c_bezier][2]);

      gimp_coords_mix (1.0,             &c_bezier[*n_c_bezier][0],
                       (t1 - t0) / 3.0, &c_bezier[*n_c_bezier][1],
                       &c_bezier[*n_c_bezier][1]);
      gimp_coords_mix (1.0,             &c_bezier[*n_c_bezier][3],
                       (t0 - t1) / 3.0, &c_bezier[*n_c_bezier][2],
                       &c_bezier[*n_c_bezier][2]);

      (*n_c_bezier)++;
    }
}

/* transforms the cubic bezier segment, defined by the four control points
 * 'bezier', by 'matrix', subdividing it as necessary to avoid diverging too
 * much from the real transformed curve.  at most 'depth' subdivisions are
 * performed.
 *
 * appends the transformed sequence of bezier segments to 't_beziers'.
 *
 * 'bezier' shall be fully clipped to the near plane.
 */
static void
transform_bezier_coords (const GimpMatrix3 *matrix,
                         const GimpCoords   bezier[4],
                         GQueue            *t_beziers,
                         gint               depth)
{
  GimpCoords *t_bezier;
  gint        n;

  /* check if we need to split the segment */
  if (depth > 0)
    {
      GimpVector2 v[4];
      GimpVector2 c[2];
      GimpVector2 b;
      gint        i;

      for (i = 0; i < 4; i++)
        v[i] = (GimpVector2) { bezier[i].x, bezier[i].y };

      gimp_vector2_sub (&c[0], &v[1], &v[0]);
      gimp_vector2_sub (&c[1], &v[2], &v[3]);

      gimp_vector2_sub (&b, &v[3], &v[0]);
      gimp_vector2_mul (&b, 1.0 / gimp_vector2_inner_product (&b, &b));

      for (i = 0; i < 2; i++)
        {
          /* split the segment if one of the control points is too far from the
           * line connecting the anchors
           */
          if (fabs (gimp_vector2_cross_product (&c[i], &b).x) > 0.5)
            {
              GimpCoords mid_position;
              GimpCoords mid_velocity;
              GimpCoords sub[4];

              gimp_coords_interpolate_bezier_at (bezier, 0.5,
                                                 &mid_position, &mid_velocity);

              /* first half */
              sub[0] = bezier[0];
              sub[3] = mid_position;

              gimp_coords_mix (0.5,        &sub[0],
                               0.5,        &bezier[1],
                                           &sub[1]);
              gimp_coords_mix (1.0,        &sub[3],
                               -1.0 / 6.0, &mid_velocity,
                                           &sub[2]);

              transform_bezier_coords (matrix, sub, t_beziers, depth - 1);

              /* second half */
              sub[0] = mid_position;
              sub[3] = bezier[3];

              gimp_coords_mix (1.0,        &sub[0],
                               +1.0 / 6.0, &mid_velocity,
                                           &sub[1]);
              gimp_coords_mix (0.5,        &sub[3],
                               0.5,        &bezier[2],
                                           &sub[2]);

              transform_bezier_coords (matrix, sub, t_beziers, depth - 1);

              return;
            }
        }
    }

  /* transform the segment by transforming each of the individual points.  note
   * that, for non-affine transforms, this is only an approximation of the real
   * transformed curve, but due to subdivision it should be good enough.
   */
  t_bezier = g_new (GimpCoords, 4);

  /* note that while the segments themselves are clipped to the near plane,
   * their control points may still get transformed behind the camera.  we
   * therefore clip the control points to the near plane as well, which is not
   * too meaningful, but avoids erroneously transforming them behind the
   * camera.
   */
  gimp_transform_polygon_coords (matrix, bezier, 2, FALSE,
                                 t_bezier, &n);
  gimp_transform_polygon_coords (matrix, bezier + 2, 2, FALSE,
                                 t_bezier + 2, &n);

  g_queue_push_tail (t_beziers, t_bezier);
}

/* transforms the cubic bezier segment, defined by the four control points
 * 'bezier', by 'matrix', performing clipping by the near plane and subdividing
 * as necessary.
 *
 * returns the transformed set of bezier-segment sequences in 't_beziers', as
 * GQueues of GimpCoords[4] bezier-segments, and the number of sequences in
 * 'n_t_beziers'.  the minimal possible number of transformed sequences is 0,
 * which happens when the entire segment is clipped.  the maximal possible
 * number of transformed sequences is 2.  each sequence has at least one
 * segment.
 *
 * if the first transformed segment is an initial segment of 'bezier', sets
 * '*start_in' to TRUE, otherwise to FALSE.  if the last transformed segment is
 * a final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE.
 */
void
gimp_transform_bezier_coords (const GimpMatrix3 *matrix,
                              const GimpCoords   bezier[4],
                              GQueue            *t_beziers[2],
                              gint              *n_t_beziers,
                              gboolean          *start_in,
                              gboolean          *end_in)
{
  GimpCoords c_bezier[2][4];
  gint       i;

  g_return_if_fail (matrix != NULL);
  g_return_if_fail (bezier != NULL);
  g_return_if_fail (t_beziers != NULL);
  g_return_if_fail (n_t_beziers != NULL);
  g_return_if_fail (start_in != NULL);
  g_return_if_fail (end_in != NULL);

  /* if the matrix is affine, transform the easy way */
  if (gimp_matrix3_is_affine (matrix))
    {
      GimpCoords *t_bezier;

      t_beziers[0] = g_queue_new ();
      *n_t_beziers = 1;

      t_bezier = g_new (GimpCoords, 1);
      g_queue_push_tail (t_beziers[0], t_bezier);

      for (i = 0; i < 4; i++)
        {
          t_bezier[i] = bezier[i];

          gimp_matrix3_transform_point (matrix,
                                        bezier[i].x,    bezier[i].y,
                                        &t_bezier[i].x, &t_bezier[i].y);
        }

      return;
    }

  /* clip the segment to the near plane */
  clip_bezier (bezier,
               matrix->coeff[2][0],
               matrix->coeff[2][1],
               matrix->coeff[2][2] - GIMP_TRANSFORM_NEAR_Z,
               c_bezier, n_t_beziers,
               start_in, end_in);

  /* transform each of the resulting segments */
  for (i = 0; i < *n_t_beziers; i++)
    {
      t_beziers[i] = g_queue_new ();

      transform_bezier_coords (matrix, c_bezier[i], t_beziers[i], 3);
    }
}
