/******************************************************/
/* Apply mapping and shading on the whole input image */
/******************************************************/

#include "config.h"

#include <string.h>

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "map-object-main.h"
#include "map-object-image.h"
#include "map-object-shade.h"
#include "map-object-apply.h"

#include "libgimp/stdplugins-intl.h"


/*************/
/* Main loop */
/*************/

gdouble       imat[4][4];
gfloat        rotmat[16];
static gfloat a[16], b[16];

void
init_compute (void)
{
  gint i;

  switch (mapvals.maptype)
    {
      case MAP_SPHERE:

        /* Rotate the equator/northpole axis */
        /* ================================= */

        gimp_vector3_set (&mapvals.firstaxis,  0.0, 0.0, -1.0);
        gimp_vector3_set (&mapvals.secondaxis, 0.0, 1.0,  0.0);

        gimp_vector3_rotate (&mapvals.firstaxis,
                             gimp_deg_to_rad (mapvals.alpha),
                             gimp_deg_to_rad (mapvals.beta),
                             gimp_deg_to_rad (mapvals.gamma));
        gimp_vector3_rotate (&mapvals.secondaxis,
                             gimp_deg_to_rad (mapvals.alpha),
                             gimp_deg_to_rad (mapvals.beta),
                             gimp_deg_to_rad (mapvals.gamma));

        /* Compute the 2D bounding box of the sphere spanned by the axis */
        /* ============================================================= */

        compute_bounding_box ();

        get_ray_color = get_ray_color_sphere;

        break;

      case MAP_PLANE:

        /* Rotate the plane axis */
        /* ===================== */

        gimp_vector3_set (&mapvals.firstaxis,  1.0, 0.0, 0.0);
        gimp_vector3_set (&mapvals.secondaxis, 0.0, 1.0, 0.0);
        gimp_vector3_set (&mapvals.normal,     0.0, 0.0, 1.0);

        gimp_vector3_rotate (&mapvals.firstaxis,
                             gimp_deg_to_rad (mapvals.alpha),
                             gimp_deg_to_rad (mapvals.beta),
                             gimp_deg_to_rad (mapvals.gamma));
        gimp_vector3_rotate (&mapvals.secondaxis,
                             gimp_deg_to_rad (mapvals.alpha),
                             gimp_deg_to_rad (mapvals.beta),
                             gimp_deg_to_rad (mapvals.gamma));

        mapvals.normal = gimp_vector3_cross_product (&mapvals.firstaxis,
                                                     &mapvals.secondaxis);

        if (mapvals.normal.z < 0.0)
          gimp_vector3_mul (&mapvals.normal, -1.0);

        /* Initialize intersection matrix */
        /* ============================== */

        imat[0][1] = -mapvals.firstaxis.x;
        imat[1][1] = -mapvals.firstaxis.y;
        imat[2][1] = -mapvals.firstaxis.z;

        imat[0][2] = -mapvals.secondaxis.x;
        imat[1][2] = -mapvals.secondaxis.y;
        imat[2][2] = -mapvals.secondaxis.z;

        imat[0][3] = mapvals.position.x - mapvals.viewpoint.x;
        imat[1][3] = mapvals.position.y - mapvals.viewpoint.y;
        imat[2][3] = mapvals.position.z - mapvals.viewpoint.z;

        get_ray_color = get_ray_color_plane;

        break;

      case MAP_BOX:
        get_ray_color = get_ray_color_box;

        gimp_vector3_set (&mapvals.firstaxis,  1.0, 0.0, 0.0);
        gimp_vector3_set (&mapvals.secondaxis, 0.0, 1.0, 0.0);
        gimp_vector3_set (&mapvals.normal,     0.0, 0.0, 1.0);

        ident_mat (rotmat);

        rotatemat (mapvals.alpha, &mapvals.firstaxis, a);

        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        rotatemat (mapvals.beta, &mapvals.secondaxis, a);
        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        rotatemat (mapvals.gamma, &mapvals.normal, a);
        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        /* Set up pixel regions for the box face images */
        /* ============================================ */

        for (i = 0; i < 6; i++)
          {
            box_drawables[i] = gimp_drawable_get (mapvals.boxmap_id[i]);

            gimp_pixel_rgn_init (&box_regions[i], box_drawables[i],
                                 0, 0,
                                 box_drawables[i]->width,
                                 box_drawables[i]->height,
                                 FALSE, FALSE);
          }

        break;

      case MAP_CYLINDER:
        get_ray_color = get_ray_color_cylinder;

        gimp_vector3_set (&mapvals.firstaxis,  1.0, 0.0, 0.0);
        gimp_vector3_set (&mapvals.secondaxis, 0.0, 1.0, 0.0);
        gimp_vector3_set (&mapvals.normal,     0.0, 0.0, 1.0);

        ident_mat (rotmat);

        rotatemat (mapvals.alpha, &mapvals.firstaxis, a);

        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        rotatemat (mapvals.beta, &mapvals.secondaxis, a);
        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        rotatemat (mapvals.gamma, &mapvals.normal, a);
        matmul (a, rotmat, b);

        memcpy (rotmat, b, sizeof (gfloat) * 16);

        /* Set up pixel regions for the cylinder cap images */
        /* ================================================ */

        for (i = 0; i < 2; i++)
          {
            cylinder_drawables[i] =
              gimp_drawable_get (mapvals.cylindermap_id[i]);

            gimp_pixel_rgn_init (&cylinder_regions[i], cylinder_drawables[i],
                                 0, 0,
                                 cylinder_drawables[i]->width,
                                 cylinder_drawables[i]->height,
                                 FALSE, FALSE);
          }

        break;
    }

  max_depth = (gint) mapvals.maxdepth;
}

static void
render (gdouble   x,
        gdouble   y,
        GimpRGB  *col,
        gpointer  data)
{
  GimpVector3 pos;

  pos.x = x / (gdouble) width;
  pos.y = y / (gdouble) height;
  pos.z = 0.0;

  *col = get_ray_color (&pos);
}

static void
show_progress (gint     min,
               gint     max,
               gint     curr,
               gpointer data)
{
  gimp_progress_update ((gdouble) curr / (gdouble) max);
}

/**************************************************/
/* Performs map-to-sphere on the whole input image */
/* and updates or creates a new GIMP image.       */
/**************************************************/

void
compute_image (void)
{
  gint         xcount, ycount;
  GimpRGB      color;
  glong        progress_counter = 0;
  GimpVector3  p;
  gint32       new_image_id = -1;
  gint32       new_layer_id = -1;
  gboolean     insert_layer = FALSE;

  init_compute ();

  if (mapvals.create_new_image)
    {
      new_image_id = gimp_image_new (width, height, GIMP_RGB);
    }
  else
    {
      new_image_id = image_id;
    }

  gimp_image_undo_group_start (new_image_id);

  if (mapvals.create_new_image ||
      mapvals.create_new_layer ||
      (mapvals.transparent_background &&
       output_drawable->bpp != 4))
    {
      gchar *layername[] = {_("Map to plane"), _("Map to sphere"), _("Map to box"),
                            _("Map to cylinder"), _("Background")};

      new_layer_id = gimp_layer_new (new_image_id,
                                     layername[mapvals.create_new_image ? 4 :
                                               mapvals.maptype],
                                     width, height,
                                     mapvals.transparent_background ?
                                     GIMP_RGBA_IMAGE :
                                     GIMP_RGB_IMAGE,
                                     100.0,
                                     GIMP_LAYER_MODE_NORMAL_LEGACY);

      insert_layer = TRUE;
      output_drawable = gimp_drawable_get (new_layer_id);
    }

  gimp_pixel_rgn_init (&dest_region, output_drawable,
                       0, 0, width, height, TRUE, TRUE);

  switch (mapvals.maptype)
    {
      case MAP_PLANE:
        gimp_progress_init (_("Map to plane"));
        break;
      case MAP_SPHERE:
        gimp_progress_init (_("Map to sphere"));
        break;
      case MAP_BOX:
        gimp_progress_init (_("Map to box"));
        break;
      case MAP_CYLINDER:
        gimp_progress_init (_("Map to cylinder"));
        break;
    }

  if (mapvals.antialiasing == FALSE)
    {
      for (ycount = 0; ycount < height; ycount++)
        {
          for (xcount = 0; xcount < width; xcount++)
            {
              p = int_to_pos (xcount, ycount);
              color = (* get_ray_color) (&p);
              poke (xcount, ycount, &color, NULL);

              if ((progress_counter++ % width) == 0)
                gimp_progress_update ((gdouble) progress_counter /
                                      (gdouble) maxcounter);
            }
        }
    }
  else
    {
      gimp_adaptive_supersample_area (0, 0,
                                      width - 1, height - 1,
                                      max_depth,
                                      mapvals.pixeltreshold,
                                      render,
                                      NULL,
                                      poke,
                                      NULL,
                                      show_progress,
                                      NULL);
    }
  gimp_progress_update (1.0);

  /* Update the region */
  /* ================= */

  gimp_drawable_flush (output_drawable);
  if (insert_layer)
    gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
  gimp_drawable_merge_shadow (output_drawable->drawable_id, TRUE);
  gimp_drawable_update (output_drawable->drawable_id, 0, 0, width, height);

  if (new_image_id != image_id)
    {
      gimp_display_new (new_image_id);
      gimp_displays_flush ();
      gimp_drawable_detach (output_drawable);
    }

  gimp_image_undo_group_end (new_image_id);
}
