/* mirrorblob  Copyright (c) 2003 Jon Dowdall <jon.dowdall@bigpond.com> */
/*
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 23-Sep-2003:  jon.dowdall@bigpond.com  Created module "blob"
 * 19-Oct-2003:  jon.dowdall@bigpond.com  Added texturing
 * 21-Oct-2003:                           Renamed to mirrorblob
 * 10-Feb-2004:  jon.dowdall@bigpond.com  Added motion blur
 * 28-Jan-2006:  jon.dowdall@bigpond.com  Big clean up and bug fixes
 * 13-Apr-2009:  jon.dowdall@gmail.com    Fixed Mac version
 *
 * The mirrorblob screensaver draws a pulsing blob on the screen.  Options
 * include adding a background (via screen_to_texture), texturing the blob,
 * making the blob semi-transparent and varying the resolution of the blob
 * tessellation.
 *
 * The blob was inspired by a lavalamp is in no way a simulation.  The code is
 * just an attempt to generate some eye-candy.
 *
 * Much of xmirrorblob code framework is taken from the pulsar module by David
 * Konerding and the glslideshow by Mike Oliphant and Jamie Zawinski.
 *
 */

#include <math.h>

#ifdef STANDALONE
#define DEFAULTS "*delay:             " DEF_DELAY "\n"                      \
                 "*showFPS:           " DEF_FPS   "\n"                      \
                 "*useSHM:              True       \n"                      \
                 "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
                 "*grabDesktopImages:   True  \n"                           \
                 "*chooseRandomImages:  True  \n"                           \
		 "*suppressRotationAnimation: True\n"                       \

# define release_mirrorblob 0
# include "xlockmore.h"
#else /* !STANDALONE */
# include "xlock.h"        /* from the xlockmore distribution */
#endif /* !STANDALONE */

#ifdef USE_GL /* whole file */


#define DEF_DELAY            "10000"
#define DEF_FPS              "False"
#define DEF_WIRE             "False"
#define DEF_BLEND            "1.0"
#define DEF_FOG              "False"
#define DEF_ANTIALIAS        "False"
#define DEF_WALLS            "False"
#define DEF_COLOUR           "False"
#define DEF_ASYNC            "True"
#define DEF_TEXTURE          "True"
#define DEF_OFFSET_TEXTURE   "False"
#define DEF_PAINT_BACKGROUND "True"
#define DEF_RESOLUTION       "90"
#define DEF_BUMPS            "10"
#define DEF_MOTION_BLUR      "0.0"
#define DEF_INCREMENTAL      "0"
#define DEF_HOLD_TIME        "30.0"
#define DEF_FADE_TIME        "5.0"
#define DEF_ZOOM             "1.0"

#include "gltrackball.h"
#include "grab-ximage.h"
#include <sys/time.h>

#define PI M_PI

/* Options from command line */
static GLfloat blend;
static Bool wireframe;
static Bool do_fog;
static Bool do_antialias;
static Bool do_walls;
static Bool do_texture;
static Bool do_paint_background;
static Bool do_colour;
static Bool offset_texture;
static int resolution;
static int bumps;
static float motion_blur;
static float fade_time;
static float hold_time;
static float zoom;

/* Internal parameters based on supplied options */
static Bool culling;
static Bool load_textures;

static XrmOptionDescRec opts[] = {
    {"-wire",             ".blob.wire",             XrmoptionNoArg, "true" },
    {"+wire",             ".blob.wire",             XrmoptionNoArg, "false" },
    {"-blend",            ".blob.blend",            XrmoptionSepArg, 0 },
    {"-fog",              ".blob.fog",              XrmoptionNoArg, "true" },
    {"+fog",              ".blob.fog",              XrmoptionNoArg, "false" },
    {"-antialias",        ".blob.antialias",        XrmoptionNoArg, "true" },
    {"+antialias",        ".blob.antialias",        XrmoptionNoArg, "false" },
    {"-walls",            ".blob.walls",            XrmoptionNoArg, "true" },
    {"+walls",            ".blob.walls",            XrmoptionNoArg, "false" },
    {"-texture",          ".blob.texture",          XrmoptionNoArg, "true" },
    {"+texture",          ".blob.texture",          XrmoptionNoArg, "false" },
    {"-colour",           ".blob.colour",           XrmoptionNoArg, "true" },
    {"+colour",           ".blob.colour",           XrmoptionNoArg, "false" },
    {"-offset-texture",   ".blob.offsetTexture",    XrmoptionNoArg, "true" },
    {"+offset-texture",   ".blob.offsetTexture",    XrmoptionNoArg, "false" },
    {"-paint-background", ".blob.paintBackground",  XrmoptionNoArg, "true" },
    {"+paint-background", ".blob.paintBackground",  XrmoptionNoArg, "false" },
    {"-resolution",       ".blob.resolution",       XrmoptionSepArg, NULL },
    {"-bumps",            ".blob.bumps",            XrmoptionSepArg, NULL },
    {"-motion-blur",      ".blob.motionBlur",       XrmoptionSepArg, 0 },
    {"-fade-time",        ".blob.fadeTime",         XrmoptionSepArg, 0 },
    {"-hold-time",        ".blob.holdTime",         XrmoptionSepArg, 0 },
    {"-zoom",             ".blob.zoom",             XrmoptionSepArg, 0 },
};

static argtype vars[] = {
    {&wireframe,    "wire",         "Wire",      DEF_WIRE,      t_Bool},
    {&blend,        "blend",        "Blend",     DEF_BLEND,     t_Float},
    {&do_fog,       "fog",          "Fog",       DEF_FOG,       t_Bool},
    {&do_antialias, "antialias",    "Antialias", DEF_ANTIALIAS, t_Bool},
    {&do_walls,     "walls",        "Walls",     DEF_WALLS,     t_Bool},
    {&do_texture,   "texture",      "Texture",   DEF_TEXTURE,   t_Bool},
    {&do_colour,    "colour",       "Colour",    DEF_COLOUR,   t_Bool},
    {&offset_texture, "offsetTexture","OffsetTexture", DEF_OFFSET_TEXTURE, t_Bool},
    {&do_paint_background,"paintBackground","PaintBackground", DEF_PAINT_BACKGROUND, t_Bool},
    {&resolution,   "resolution",   "Resolution",   DEF_RESOLUTION,   t_Int},
    {&bumps,        "bumps",        "Bump",         DEF_BUMPS, t_Int},
    {&motion_blur,  "motionBlur",   "MotionBlur",   DEF_MOTION_BLUR,  t_Float},
    {&fade_time,    "fadeTime",     "FadeTime",     DEF_FADE_TIME,    t_Float},
    {&hold_time,    "holdTime",     "HoldTime",     DEF_HOLD_TIME,    t_Float},
    {&zoom,         "zoom",         "Zoom",         DEF_ZOOM,         t_Float},
};


static OptionStruct desc[] =
{
    {"-/+ wire", "whether to do use wireframe instead of filled (faster)"},
    {"-/+ blend", "whether to do enable blending (slower)"},
    {"-/+ fog", "whether to do enable fog (slower)"},
    {"-/+ antialias", "whether to do enable antialiased lines (slower)"},
    {"-/+ walls", "whether to add walls to the blob space (slower)"},
    {"-/+ texture", "whether to add a texture to the blob (slower)"},
    {"-/+ colour", "whether to colour the blob"},
    {"-/+ offset_texture", "whether to offset texture co-ordinates"},
    {"-/+ paint_background", "whether to display a background texture (slower)"},
    {"-resolution", "Resolution of blob tesselation"},
    {"-bumps", "Number of bumps used to disturb blob"},
    {"-motion_blur", "Fade blob images (higher number = faster fade)"},
    {"-fade_time", "Number of frames to transition to next image"},
    {"-hold_time", "Number of frames before next image"},
};

ENTRYPOINT ModeSpecOpt mirrorblob_opts = {countof(opts), opts, countof(vars), vars, desc};

#ifdef USE_MODULES
ModStruct   mirrorblob_description =
{"mirrorblob", "init_mirrorblob", "draw_mirrorblob", NULL,
 "draw_mirrorblob", "init_mirrorblob", "free_mirrorblob", &mirrorblob_opts,
 1000, 1, 2, 1, 4, 1.0, "",
 "OpenGL mirrorblob", 0, NULL};
#endif

/*****************************************************************************
 * Types used in blob code
 *****************************************************************************/

typedef struct
{
  GLfloat x, y;
} Vector2D;

typedef struct
{
  GLfloat x, y, z;
} Vector3D;

typedef struct
{
  GLfloat w;
  GLfloat x;
  GLfloat y;
  GLfloat z;
} Quaternion;

typedef struct
{
  GLubyte red, green, blue, alpha;
} Colour;

typedef struct
{
  Vector3D initial_position;
  Vector3D position;
  Vector3D normal;
} Node_Data;

typedef struct
{
  int node1, node2, node3;
  Vector3D normal;
  double length1, length2, length3;
} Face_Data;

/* Structure to hold data about bumps used to distortion sphere */
typedef struct
{
  double cx, cy, cpower, csize;
  double ax, ay, power, size;
  double mx, my, mpower, msize;
  double vx, vy, vpower, vsize;
  Vector3D pos;
} Bump_Data;

/* Vertices of a tetrahedron */
#define sqrt_3 0.5773502692
/*#undef sqrt_3
#define sqrt_3 1.0*/
#define PPP {  sqrt_3,  sqrt_3,  sqrt_3 }       /* +X, +Y, +Z */
#define MMP { -sqrt_3, -sqrt_3,  sqrt_3 }       /* -X, -Y, +Z */
#define MPM { -sqrt_3,  sqrt_3, -sqrt_3 }       /* -X, +Y, -Z */
#define PMM {  sqrt_3, -sqrt_3, -sqrt_3 }       /* +X, -Y, -Z */

/* Structure describing a tetrahedron */
static Vector3D tetrahedron[4][3] = {
    {PPP, MMP, MPM},
    {PMM, MPM, MMP},
    {PPP, MPM, PMM},
    {PPP, PMM, MMP}
};

/*****************************************************************************
 * Static blob data
 *****************************************************************************/

static const Vector3D zero_vector = { 0.0, 0.0, 0.0 };

/* Use 2 textures to allow a gradual fade between images */
#define NUM_TEXTURES 2
#define BUMP_ARRAY_SIZE 1024

typedef enum
{
  INITIALISING,
  HOLDING,
  LOADING,
  TRANSITIONING
} Frame_State;

/* structure for holding the mirrorblob data */
typedef struct {
  int screen_width, screen_height;
  GLXContext *glx_context;
  Window window;
  XColor fg, bg;

  /* Parameters controlling the position of the blob as a whole */
  Vector3D blob_center;
  Vector3D blob_anchor;
  Vector3D blob_velocity;
  Vector3D blob_force;

  /* Count of the total number of nodes and faces used to tesselate the blob */
  int num_nodes;
  int num_faces;

  Node_Data *nodes;
  Face_Data *faces;
  
  Vector3D *dots;
  Vector3D *normals;
  Colour   *colours;
  Vector2D *tex_coords;

  /* Pointer to the bump function results */
  double *bump_shape, *wall_shape;

  Bump_Data *bump_data;

  /* Use 2 textures to allow a gradual fade between images */
  int current_texture;

  /* Ratio of used texture size to total texture size */
  GLfloat tex_width[NUM_TEXTURES], tex_height[NUM_TEXTURES];
  GLuint textures[NUM_TEXTURES];

  Frame_State state;
  double state_start_time;

  int colour_cycle;

  Bool mipmap_p;
  Bool waiting_for_image_p;
  Bool first_image_p;

  trackball_state *trackball;
  int button_down;

} mirrorblobstruct;

static mirrorblobstruct *Mirrorblob = NULL;

/******************************************************************************
 *
 * Returns the current time in seconds as a double.  Shamelessly borrowed from
 * glslideshow.
 *
 */
static double
double_time (void)
{
  struct timeval now;
# ifdef GETTIMEOFDAY_TWO_ARGS
  struct timezone tzp;
  gettimeofday(&now, &tzp);
# else
  gettimeofday(&now);
# endif

  return (now.tv_sec + ((double) now.tv_usec * 0.000001));
}

/******************************************************************************
 *
 * Change to the projection matrix and set our viewing volume.
 *
 */
static void
reset_projection(int width, int height)
{
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (60.0, 1.0, 1.0, 1024.0 );
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

/******************************************************************************
 *
 * Calculate the dot product of two vectors u and v
 * Dot product u.v = |u||v|cos(theta)
 * Where theta = angle between u and v
 */
static inline double
dot (const Vector3D u, const Vector3D v)
{
  return (u.x * v.x) + (u.y * v.y) + (u.z * v.z);
}

/******************************************************************************
 *
 * Calculate the cross product of two vectors.
 * Gives a vector perpendicular to u and v with magnitude |u||v|sin(theta)
 * Where theta = angle between u and v
 */
static inline Vector3D
cross (const Vector3D u, const Vector3D v)
{
  Vector3D result;

  result.x = (u.y * v.z - u.z * v.y);
  result.y = (u.z * v.x - u.x * v.z);
  result.z = (u.x * v.y - u.y * v.x);

  return result;
}

/******************************************************************************
 *
 * Add vector v to vector u
 */
static inline void
add (Vector3D *u, const Vector3D v)
{
  u->x = u->x + v.x;
  u->y = u->y + v.y;
  u->z = u->z + v.z;
}

/******************************************************************************
 *
 * Subtract vector v from vector u
 */
static inline Vector3D
subtract (const Vector3D u, const Vector3D v)
{
  Vector3D result;

  result.x = u.x - v.x;
  result.y = u.y - v.y;
  result.z = u.z - v.z;

  return result;
}

/******************************************************************************
 *
 * multiply vector v by scalar s
 */
static inline Vector3D
scale (const Vector3D v, const double s)
{
  Vector3D result;
    
  result.x = v.x * s;
  result.y = v.y * s;
  result.z = v.z * s;
  return result;
}

/******************************************************************************
 *
 * normalise vector v
 */
static inline Vector3D
normalise (const Vector3D v)
{
  Vector3D result;
  double magnitude;

  magnitude = sqrt (dot(v, v));

  if (magnitude > 1e-300)
    {
      result = scale (v, 1.0 / magnitude);
    }
  else
    {
      printf("zero\n");
      result = zero_vector;
    }
  return result;
}

/******************************************************************************
 *
 * Calculate the transform matrix for the given quaternion
 */
static void
quaternion_transform (Quaternion q, GLfloat * transform)
{
  GLfloat x, y, z, w;
  x = q.x;
  y = q.y;
  z = q.z;
  w = q.w;

  transform[0] = (w * w) + (x * x) - (y * y) - (z * z);
  transform[1] = (2.0 * x * y) + (2.0 * w * z);
  transform[2] = (2.0 * x * z) - (2.0 * w * y);
  transform[3] = 0.0;

  transform[4] = (2.0 * x * y) - (2.0 * w * z);
  transform[5] = (w * w) - (x * x) + (y * y) - (z * z);
  transform[6] = (2.0 * y * z) + (2.0 * w * x);
  transform[7] = 0.0;

  transform[8] = (2.0 * x * z) + (2.0 * w * y);
  transform[9] = (2.0 * y * z) - (2.0 * w * x);
  transform[10] = (w * w) - (x * x) - (y * y) + (z * z);
  transform[11] = 0.0;

  transform[12] = 0.0;
  transform[13] = 0.0;
  transform[14] = 0.0;
  transform[15] = (w * w) + (x * x) + (y * y) + (z * z);
}

/******************************************************************************
 *
 * Apply a matrix transform to the given vector
 */
static inline Vector3D
vector_transform (Vector3D u, GLfloat * t)
{
  Vector3D result;

  result.x = (u.x * t[0] + u.y * t[4] + u.z * t[8] + 1.0 * t[12]);
  result.y = (u.x * t[1] + u.y * t[5] + u.z * t[9] + 1.0 * t[13]);
  result.z = (u.x * t[2] + u.y * t[6] + u.z * t[10] + 1.0 * t[14]);

  return result;
}

/******************************************************************************
 *
 * Return a node that is on an arc between node1 and node2, where distance
 * is the proportion of the distance from node1 to the total arc.
 */
static Vector3D
partial (Vector3D node1, Vector3D node2, double distance)
{
  Vector3D result;
  Vector3D rotation_axis;
  GLfloat transformation[16];
  double angle;
  Quaternion rotation;

  rotation_axis = normalise (cross (node1, node2));
  angle = acos (dot (node1, node2)) * distance;

  rotation.x = rotation_axis.x * sin (angle / 2.0);
  rotation.y = rotation_axis.y * sin (angle / 2.0);
  rotation.z = rotation_axis.z * sin (angle / 2.0);
  rotation.w = cos (angle / 2.0);

  quaternion_transform (rotation, transformation);

  result = vector_transform (node1, transformation);

  return result;
}

/****************************************************************************
 *
 * Callback indicating a texture has loaded
 */
static void
image_loaded_cb (const char *filename, XRectangle *geometry,
                 int image_width, int image_height, 
                 int texture_width, int texture_height,
                 void *closure)
{
  mirrorblobstruct *mp = (mirrorblobstruct *) closure;
  GLint texid = -1;
  int texture_index = -1;
  int i;

  glGetIntegerv (GL_TEXTURE_BINDING_2D, &texid);
  if (texid < 0) abort();

  for (i = 0; i < NUM_TEXTURES; i++) {
    if (mp->textures[i] == texid) {
      texture_index = i;
      break;
    }
  }
  if (texture_index < 0) abort();

  mp->tex_width [texture_index] =  (GLfloat) image_width  / texture_width;
  mp->tex_height[texture_index] = -(GLfloat) image_height / texture_height;

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                   (mp->mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  mp->waiting_for_image_p = False;
  mp->first_image_p = True;
}

/* Load a new file into a texture
 */
static void
grab_texture(ModeInfo *mi, int texture_index)
{
  mirrorblobstruct *mp = &Mirrorblob[MI_SCREEN(mi)];
	
  {
    int w = (MI_WIDTH(mi)  / 2) - 1;
    int h = (MI_HEIGHT(mi) / 2) - 1;
    if (w <= 10) w = 10;
    if (h <= 10) h = 10;
	
    mp->waiting_for_image_p = True;
    mp->mipmap_p = True;
    load_texture_async (mi->xgwa.screen, mi->window,
                        *mp->glx_context, w, h, mp->mipmap_p, 
                        mp->textures[texture_index],
                        image_loaded_cb, mp);
  }
}

/******************************************************************************
 *
 * Generate internal parameters based on supplied options the parameters to
 * ensure they are consistant.
 */
static void
set_parameters(void)
{
# ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
  wireframe = 0;
# endif

  /* In wire frame mode do not draw a texture */
  if (wireframe)
    {
      do_texture = False;
      blend = 1.0;
    }
    
  /* Need to load textures if either the blob or the backgound has an image */
  if (do_texture || do_paint_background)
    {
      load_textures = True;
    }
  else
    {
      load_textures = False;
    }
    
  /* If theres no texture don't calculate co-ordinates. */
  if (!do_texture)
    {
      offset_texture = False;
    }
    
  culling = True;
}

/******************************************************************************
 *
 * Initialise the openGL state data.
 */
static void
initialize_gl(ModeInfo *mi, GLsizei width, GLsizei height)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
    
  /* Lighting values */
  GLfloat ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f };

  GLfloat lightPos0[] = {500.0f, 100.0f, 200.0f, 1.0f };
  GLfloat whiteLight0[] = { 0.0f, 0.0f, 0.0f, 1.0f };
  GLfloat sourceLight0[] = { 0.6f, 0.6f, 0.6f, 1.0f };
  GLfloat specularLight0[] = { 0.8f, 0.8f, 0.9f, 1.0f };

  GLfloat lightPos1[] = {-50.0f, -100.0f, 2500.0f, 1.0f };
  GLfloat whiteLight1[] = { 0.0f, 0.0f, 0.0f, 1.0f };
  GLfloat sourceLight1[] = { 0.6f, 0.6f, 0.6f, 1.0f };
  GLfloat specularLight1[] = { 0.7f, 0.7f, 0.7f, 1.0f };

  GLfloat specref[] = { 1.0f, 1.0f, 1.0f, 1.0f };

  GLfloat fogColor[4] = { 0.4, 0.4, 0.5, 0.1 };

  /* Set the internal parameters based on the configuration settings */
  set_parameters();

  /* Set the viewport to the width and heigh of the window */
  glViewport (0, 0, width, height ); 

  if (do_antialias)
    {
      blend = 1.0;
      glEnable(GL_LINE_SMOOTH);
      glEnable(GL_POLYGON_SMOOTH);
    }

  /* The blend function is used for trasitioning between two images even when
   * blend is not selected.
   */
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
  if (do_fog)
    {
      glEnable(GL_FOG);
      glFogfv(GL_FOG_COLOR, fogColor);
      glFogf(GL_FOG_DENSITY, 0.50);
      glFogf(GL_FOG_START, 15.0);
      glFogf(GL_FOG_END, 30.0);
    }

  /* Set the shading model to smooth (Gouraud shading). */
  glShadeModel (GL_SMOOTH);

  glLightModelfv (GL_LIGHT_MODEL_AMBIENT, ambientLight);
  glLightfv (GL_LIGHT0, GL_AMBIENT, whiteLight0);
  glLightfv (GL_LIGHT0, GL_DIFFUSE, sourceLight0);
  glLightfv (GL_LIGHT0, GL_SPECULAR, specularLight0);
  glLightfv (GL_LIGHT0, GL_POSITION, lightPos0);
  glEnable (GL_LIGHT0);
  glLightfv (GL_LIGHT1, GL_AMBIENT, whiteLight1);
  glLightfv (GL_LIGHT1, GL_DIFFUSE, sourceLight1);
  glLightfv (GL_LIGHT1, GL_SPECULAR, specularLight1);
  glLightfv (GL_LIGHT1, GL_POSITION, lightPos1);
  glEnable (GL_LIGHT1);
  glEnable (GL_LIGHTING);

  /* Enable color tracking */
  glEnable (GL_COLOR_MATERIAL);

  /* Set Material properties to follow glColor values */
  glColorMaterial (GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

  /* Set all materials to have specular reflectivity */
  glMaterialfv (GL_FRONT, GL_SPECULAR, specref);
  glMateriali (GL_FRONT, GL_SHININESS, 32);

  /* Let GL implementation scale normal vectors. */
  glEnable (GL_NORMALIZE);

  /* Enable Arrays */
  if (load_textures)
    {
      glLightModeli (GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
      glEnable (GL_TEXTURE_2D);

      gp->current_texture = 0;
      glGenTextures(NUM_TEXTURES, gp->textures);
      grab_texture(mi, gp->current_texture);

      glMatrixMode (GL_TEXTURE);
      glRotated (180.0, 1.0, 0.0, 0.0);
      glMatrixMode (GL_MODELVIEW);
    }

  /* Clear the buffer since this is not done during a draw with motion blur */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

/******************************************************************************
 *
 * Initialise the openGL state data.
 */
static void
set_blob_gl_state(GLfloat alpha)
{
  if (do_antialias)
    {
      glEnable(GL_LINE_SMOOTH);
      glEnable(GL_POLYGON_SMOOTH);
    }

  if (wireframe)
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
  else
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

  /* The blend function is used for trasitioning between two images even when
   * blend is not selected.
   */
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
  /* Culling. */
  if (culling)
    {
      glCullFace (GL_BACK);
      glEnable (GL_CULL_FACE);
      glFrontFace (GL_CCW);
    }
  else
    {
      glDisable (GL_CULL_FACE);
    }
    
  if (blend < 1.0)
    {
      glEnable (GL_BLEND);
      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      /* Set the default blob colour to off-white. */
      glColor4f (0.9, 0.9, 1.0, alpha);
    }
  else
    {
      glDisable(GL_BLEND);
      glColor4f (0.9, 0.9, 1.0, 1.0);
    }
    
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
}

/******************************************************************************
 *
 * Initialise the data required to draw the blob allocating the memory as
 * necessary.
 *
 * Return 0 on success.
 */
static int
initialise_blob(mirrorblobstruct *gp,
                int width,
                int height,
                int bump_array_size)
{
  /* Loop variables */    
  int i, u, v, node, side, face, base, base2 = 0;
  int nodes_on_edge = resolution;
  Vector3D node1, node2, result;

  if (nodes_on_edge < 2)
    return -1;

  gp->num_nodes = 2 * nodes_on_edge * nodes_on_edge - 4 * nodes_on_edge + 4;
  gp->num_faces = 4 * (nodes_on_edge - 1) * (nodes_on_edge - 1);
 
  gp->nodes = (Node_Data *) malloc (gp->num_nodes * sizeof (Node_Data));
  if (!gp->nodes)
    {
      fprintf (stderr, "Couldn't allocate gp->nodes buffer\n");
      return -1;
    }

  gp->faces = (Face_Data *) malloc (gp->num_faces * sizeof (Face_Data));
  if (!gp->faces)
    {
      fprintf (stderr, "Couldn't allocate faces data buffer\n");
      return -1;
    }

  gp->bump_data = (Bump_Data *) malloc (bumps * sizeof (Bump_Data));
  if (!gp->bump_data)
    {
      fprintf(stderr, "Couldn't allocate bump data buffer\n");
      return -1;
    }

  gp->bump_shape = (double *)malloc(bump_array_size * sizeof(double));
  if (!gp->bump_shape)
    {
      fprintf(stderr, "Couldn't allocate bump buffer\n");
      return -1;
    }

  gp->wall_shape = (double *)malloc(bump_array_size * sizeof(double));
  if (!gp->wall_shape)
    {
      fprintf(stderr, "Couldn't allocate wall bump buffer\n");
      return -1;
    }

	
  gp->dots = (Vector3D *)malloc(gp->num_nodes * sizeof(Vector3D));
  if (!gp->dots)
    {
      fprintf(stderr, "Couldn't allocate nodes buffer\n");
      return -1;
    }

  gp->normals = (Vector3D *)malloc(gp->num_nodes * sizeof(Vector3D));
  if (!gp->normals)
    {
      fprintf(stderr, "Couldn't allocate normals buffer\n");
      return -1;
    }

  gp->colours = (Colour *)malloc(gp->num_nodes * sizeof(Colour));
  if (!gp->colours)
    {
      fprintf(stderr, "Couldn't allocate colours buffer\n");
      return -1;
    }

  gp->tex_coords = (Vector2D *)malloc(gp->num_nodes * sizeof(Vector2D));
  if (!gp->tex_coords)
    {
      fprintf(stderr, "Couldn't allocate gp->tex_coords buffer\n");
      return -1;
    }

	
  /* Initialise bump data */
  for (i = 0; i < bumps; i++)
    {
      gp->bump_data[i].ax = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].ay = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].power = (5.0 / pow(bumps, 0.75)) * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].size = 0.1 + 0.5 * (((double)random() / (double)RAND_MAX));

      gp->bump_data[i].pos.x = 1.5 * sin(PI * gp->bump_data[i].ay)
        * cos(PI *  gp->bump_data[i].ax);
      gp->bump_data[i].pos.y = 1.5 * cos(PI * gp->bump_data[i].ay);
      gp->bump_data[i].pos.z = 1.5 * sin(PI * gp->bump_data[i].ay)
        * sin(PI *  gp->bump_data[i].ax);

      gp->bump_data[i].cx = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].cy = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].cpower = (5.0 / pow(bumps, 0.75)) * (((double)random() / (double)RAND_MAX) - 0.5);
      gp->bump_data[i].csize = 0.35; /*0.1 + 0.25 * (((double)random() / (double)RAND_MAX));*/

      gp->bump_data[i].vx = 0.0;
      gp->bump_data[i].vy = 0.0;
      gp->bump_data[i].vpower = 0.0;
      gp->bump_data[i].vsize = 0.0;

      gp->bump_data[i].mx = 0.003 * ((double)random() / (double)RAND_MAX);
      gp->bump_data[i].my = 0.003 * ((double)random() / (double)RAND_MAX);
      gp->bump_data[i].mpower = 0.003 * ((double)random() / (double)RAND_MAX);
      gp->bump_data[i].msize = 0.003 * ((double)random() / (double)RAND_MAX);
    }

  /* Initialise lookup table of bump strength */
  for (i = 0; i < bump_array_size; i++)
    {
      double xd, xd2;
      xd = i / (double)bump_array_size;

      xd2 = 48.0 * xd * xd;
      gp->bump_shape[i] = 0.1 / (xd2 + 0.1);

      xd2 = 40.0 * xd * xd * xd * xd;
      gp->wall_shape[i] = 0.4 / (xd2 + 0.1);
    }

  node = 0;
  face = 0;
  for (side = 0; side < 4; side++)
    {
      base = node;
      if (side == 2) 
        {
          base2 = node;
        }
      /*
       * The start and end of the for loops below are modified based on the 
       * side of the tetrahedron that is being calculated to avoid duplication
       * of the gp->nodes that are on the edges of the tetrahedron. 
       */
      for (u = (side > 1); u < (nodes_on_edge - (side > 0)); u++)
        {
          node1 = partial (normalise (tetrahedron[side][0]),
                           normalise (tetrahedron[side][1]),
                           u / (double) (nodes_on_edge - 1));
          node2 = partial (normalise (tetrahedron[side][0]),
                           normalise (tetrahedron[side][2]),
                           u / (double) (nodes_on_edge - 1));

          for (v = (side > 1); v <= (u - (side > 2)); v++)
            {
              if (u > 0)
                result = partial (node1, node2, v / (double) u);
              else
                result = node1;

              gp->nodes[node].position = normalise (result);
              gp->nodes[node].initial_position = gp->nodes[node].position;
              gp->nodes[node].normal = zero_vector;
              node++;
            }
        }
 
      /*
       * Determine which nodes make up each face.  The complexity is caused 
       * by having to determine the correct nodes for the edges of the
       * tetrahedron since the common nodes on the edges are only calculated
       * once (see above).
       */
      for (u = 0; u < (nodes_on_edge - 1); u++)
        {
          for (v = 0; v <= u; v++)
            {
              {
                if (side < 2)
                  {
                    gp->faces[face].node1 = base + ((u * (u + 1)) / 2) + v;
                    gp->faces[face].node2 =
                      base + ((u + 1) * (u + 2)) / 2 + v + 1;
                    gp->faces[face].node3 =
                      base + ((u + 1) * (u + 2)) / 2 + v;

                    if ((side == 1) && (u == (nodes_on_edge - 2)))
                      {
                        gp->faces[face].node3 =
                          ((u + 1) * (u + 2)) / 2 +
                          nodes_on_edge - v - 1;
                        gp->faces[face].node2 =
                          ((u + 1) * (u + 2)) / 2 +
                          nodes_on_edge - v - 2;
                      }
                  }
                else if (side < 3)
                  {
                    gp->faces[face].node1 =
                      base + (((u - 1) * u) / 2) + v - 1;
                    gp->faces[face].node2 = base + ((u) * (u + 1)) / 2 + v;
                    gp->faces[face].node3 =
                      base + ((u) * (u + 1)) / 2 + v - 1;

                    if (u == (nodes_on_edge - 2))
                      {
                        int n = nodes_on_edge - v - 1;
                        gp->faces[face].node2 =
                          ((nodes_on_edge *
                            (nodes_on_edge + 1)) / 2) +
                          ((n - 1) * (n + 0)) / 2;
                        gp->faces[face].node3 =
                          ((nodes_on_edge *
                            (nodes_on_edge + 1)) / 2) +
                          ((n + 0) * (n + 1)) / 2;
                      }
                    if (v == 0)
                      {
                        gp->faces[face].node1 = (((u + 1) * (u + 2)) / 2) - 1;
                        gp->faces[face].node3 = (((u + 2) * (u + 3)) / 2) - 1;
                      }
                  }
                else
                  {
                    gp->faces[face].node1 =
                      base + (((u - 2) * (u - 1)) / 2) + v - 1;
                    gp->faces[face].node2 = base + ((u - 1) * u) / 2 + v;
                    gp->faces[face].node3 = base + ((u - 1) * u) / 2 + v - 1;

                    if (v == 0)
                      {
                        gp->faces[face].node1 =
                          base2 + ((u * (u + 1)) / 2) - 1;
                        gp->faces[face].node3 =
                          base2 + ((u + 1) * (u + 2)) / 2 - 1;
                      }
                    if (u == (nodes_on_edge - 2))
                      {
                        gp->faces[face].node3 =
                          ((nodes_on_edge *
                            (nodes_on_edge + 1)) / 2) +
                          ((v + 1) * (v + 2)) / 2 - 1;
                        gp->faces[face].node2 =
                          ((nodes_on_edge *
                            (nodes_on_edge + 1)) / 2) +
                          ((v + 2) * (v + 3)) / 2 - 1;
                      }
                    if (v == u)
                      {
                        gp->faces[face].node1 = (u * (u + 1)) / 2;
                        gp->faces[face].node2 = ((u + 1) * (u + 2)) / 2;
                      }
                  }
                face++;
              }

              if (v < u)
                {
                  if (side < 2)
                    {
                      gp->faces[face].node1 = base + ((u * (u + 1)) / 2) + v;
                      gp->faces[face].node2 =
                        base + ((u * (u + 1)) / 2) + v + 1;
                      gp->faces[face].node3 =
                        base + (((u + 1) * (u + 2)) / 2) + v + 1;

                      if ((side == 1) && (u == (nodes_on_edge - 2)))
                        {
                          gp->faces[face].node3 =
                            ((u + 1) * (u + 2)) / 2 +
                            nodes_on_edge - v - 2;
                        }
                    }
                  else if (side < 3)
                    {
                      gp->faces[face].node1 =
                        base + ((u * (u - 1)) / 2) + v - 1;
                      gp->faces[face].node2 = base + ((u * (u - 1)) / 2) + v;
                      gp->faces[face].node3 = base + ((u * (u + 1)) / 2) + v;

                      if (u == (nodes_on_edge - 2))
                        {
                          int n = nodes_on_edge - v - 1;
                          gp->faces[face].node3 =
                            ((nodes_on_edge *
                              (nodes_on_edge + 1)) / 2) +
                            ((n + 0) * (n - 1)) / 2;
                        }
                      if (v == 0)
                        {
                          gp->faces[face].node1 = (((u + 1) * (u + 2)) / 2) - 1;
                        }
                    }
                  else
                    {
                      gp->faces[face].node1 =
                        base + (((u - 2) * (u - 1)) / 2) + v - 1;
                      gp->faces[face].node2 =
                        base + (((u - 2) * (u - 1)) / 2) + v;
                      gp->faces[face].node3 = base + (((u - 1) * u) / 2) + v;

                      if (v == 0)
                        {
                          gp->faces[face].node1 = base2 + (u * (u + 1)) / 2 - 1;
                        }
                      if (u == (nodes_on_edge - 2))
                        {
                          gp->faces[face].node3 =
                            ((nodes_on_edge * (nodes_on_edge + 1)) / 2) +
                            ((v + 2) * (v + 3)) / 2 - 1;
                        }
                      if (v == (u - 1))
                        {
                          gp->faces[face].node2 = (u * (u + 1)) / 2;
                        }
                    }
                  face++;
                }
            }
        }
    }

  return 0;
}

/******************************************************************************
 *
 * Return the magnitude of the given vector
 */
#if 0
static inline double
length (Vector3D u)
{
  return sqrt (u.x * u.x + u.y * u.y + u.z * u.z);
}
#endif

/******************************************************************************
 *
 * Calculate the blob shape.
 */
static void
calc_blob(mirrorblobstruct *gp,
          int width,
          int height,
          int bump_array_size,
          float limit,
          double fade)
{
  /* Loop variables */
  int i, index, face;
  /* position of a node */
  Vector3D node;
  Vector3D offset;
  Vector3D bump_vector;
  int dist;

  /* Update position and strength of bumps used to distort the blob */
  for (i = 0; i < bumps; i++)
    {
      gp->bump_data[i].vx += gp->bump_data[i].mx*(gp->bump_data[i].cx - gp->bump_data[i].ax);
      gp->bump_data[i].vy += gp->bump_data[i].my*(gp->bump_data[i].cy - gp->bump_data[i].ay);
      gp->bump_data[i].vpower += gp->bump_data[i].mpower
        * (gp->bump_data[i].cpower - gp->bump_data[i].power);
      gp->bump_data[i].vsize += gp->bump_data[i].msize
        * (gp->bump_data[i].csize - gp->bump_data[i].size);

      gp->bump_data[i].ax += 0.1 * gp->bump_data[i].vx;
      gp->bump_data[i].ay += 0.1 * gp->bump_data[i].vy;
      gp->bump_data[i].power += 0.1 * gp->bump_data[i].vpower;
      gp->bump_data[i].size += 0.1 * gp->bump_data[i].vsize;

      gp->bump_data[i].pos.x = 1.0 * sin(PI * gp->bump_data[i].ay)
        * cos(PI * gp->bump_data[i].ax);
      gp->bump_data[i].pos.y = 1.0 * cos(PI * gp->bump_data[i].ay);
      gp->bump_data[i].pos.z = 1.0 * sin(PI * gp->bump_data[i].ay)
        * sin(PI * gp->bump_data[i].ax);
    }

  /* Update calculate new position for each vertex based on an offset from
   * the initial position
   */
  gp->blob_force = zero_vector;
  for (index = 0; index < gp->num_nodes; ++index)
    {
      node = gp->nodes[index].initial_position;
      gp->nodes[index].normal = node;

      offset = zero_vector;
      for ( i = 0; i < bumps; i++)
        {
          bump_vector = subtract(gp->bump_data[i].pos, node);

          dist = bump_array_size * dot(bump_vector, bump_vector) * gp->bump_data[i].size;

          if (dist < bump_array_size)
            {
              add(&offset, scale(node, gp->bump_data[i].power * gp->bump_shape[dist]));
              add(&gp->blob_force, scale(node, gp->bump_data[i].power * gp->bump_shape[dist]));
            }
        }

      add(&node, offset);
      node = scale(node, zoom);
      add(&node, gp->blob_center);

      if (do_walls)
        {
          if (node.z < -limit) node.z = -limit;
          if (node.z > limit) node.z = limit;

          dist = bump_array_size * (node.z + limit) * (node.z + limit) * 0.5;
          if (dist < bump_array_size)
            {
              node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist];
              node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist];
              gp->blob_force.z += (node.z + limit);
            }
          else
            {
              dist = bump_array_size * (node.z - limit) * (node.z - limit) * 0.5;
              if (dist < bump_array_size)
                {
                  node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist];
                  node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist];
                  gp->blob_force.z -= (node.z - limit);
                }

              if (node.y < -limit) node.y = -limit;
              if (node.y > limit) node.y = limit;

              dist = bump_array_size * (node.y + limit) * (node.y + limit) * 0.5;
              if (dist < bump_array_size)
                {
                  node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist];
                  node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist];
                  gp->blob_force.y += (node.y + limit);
                }
              else
                {
                  dist = bump_array_size * (node.y - limit) * (node.y - limit) * 0.5;
                  if (dist < bump_array_size)
                    {
                      node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist];
                      node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist];
                      gp->blob_force.y -= (node.y - limit);
                    }
                }

              if (node.x < -limit) node.x = -limit;
              if (node.x > limit) node.x = limit;

              dist = bump_array_size * (node.x + limit) * (node.x + limit) * 0.5;
              if (dist < bump_array_size)
                {
                  node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist];
                  node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist];
                  gp->blob_force.x += (node.x + limit);
                }
              else
                {
                  dist = bump_array_size * (node.x - limit) * (node.x - limit) * 0.5;
                  if (dist < bump_array_size)
                    {
                      node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist];
                      node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist];
                      gp->blob_force.x -= (node.x - limit);
                    }
                }

              if (node.y < -limit) node.y = -limit;
              if (node.y > limit) node.y = limit;
            }
        }
      gp->dots[index] = node;
    }

  /* Determine the normal for each face */
  for (face = 0; face < gp->num_faces; face++)
    {
      /* Use pointers to indexed nodes to help readability */
      int index1 = gp->faces[face].node1;
      int index2 = gp->faces[face].node2;
      int index3 = gp->faces[face].node3;

      gp->faces[face].normal = cross(subtract(gp->dots[index2], gp->dots[index1]),
                                     subtract(gp->dots[index3], gp->dots[index1]));
            
      /* Add the normal for the face onto the normal for the verticies of
         the face */
      add(&gp->nodes[index1].normal, gp->faces[face].normal);
      add(&gp->nodes[index2].normal, gp->faces[face].normal);
      add(&gp->nodes[index3].normal, gp->faces[face].normal);
    }

  /* Use the normal to set the colour and texture */
  if (do_colour || do_texture)
    {
      for (index = 0; index < gp->num_nodes; ++index)
        {
          gp->normals[index] = normalise(gp->nodes[index].normal);
   
          if (do_colour)
            {
              gp->colours[index].red = (int)(255.0 * fabs(gp->normals[index].x));
              gp->colours[index].green = (int)(255.0 * fabs(gp->normals[index].y));
              gp->colours[index].blue = (int)(255.0 * fabs(gp->normals[index].z));
              gp->colours[index].alpha = (int)(255.0 * fade);
            }
          if (do_texture)
            {
              if (offset_texture)
                {
                  const float cube_size = 100.0;
                  Vector3D eye = {0.0, 0.0, 50.0};
                  Vector3D eye_r = normalise(subtract(gp->dots[index], eye));
                  Vector3D reference = subtract(eye_r, scale(gp->normals[index], 2.0 * dot(eye_r, gp->normals[index])));
                  double x = 0.0;
                  double y = 0.0;
                  double n, n_min = 10000.0, sign = 1.0;
                  if (fabs(reference.z) > 1e-9)
                    {
                      n = (cube_size - gp->dots[index].z) / reference.z;
                      if (n < 0.0)
                        {
                          n = (-cube_size - gp->dots[index].z) / reference.z;
                          sign = 3.0;
                        }
                      if (n > 0.0)
                        {
                          x = sign * (gp->dots[index].x + n * reference.x);
                          y = sign * (gp->dots[index].y + n * reference.y);
                          n_min = n;
                        }
                    }
                  if (fabs(reference.x) > 1e-9)
                    {
                      n = (cube_size - gp->dots[index].x) / reference.x;
                      sign = 1.0;
                      if (n < 0.0)
                        {
                          n = (-cube_size - gp->dots[index].x) / reference.x;
                          sign = -1.0;
                        }
                      if ((n > 0.0) && (n < n_min))
                        {
                          x = sign * (2.0 * cube_size - (gp->dots[index].z + n * reference.z));
                          y = sign * x * (gp->dots[index].y + n * reference.y) / cube_size;
                          n_min = n;
                        }
                    }
                  if (fabs(reference.y) > 1e-9)
                    {
                      n = (cube_size - gp->dots[index].y) / reference.y;
                      sign = 1.0;
                      if (n < 0.0)
                        {
                          n = (-cube_size - gp->dots[index].y) / reference.y;
                          sign = -1.0;
                        }
                      if ((n > 0.0) && (n < n_min))
                        {
                          y = sign * (2.0 * cube_size -( gp->dots[index].z + n * reference.z));
                          x = sign * y * (gp->dots[index].x + n * reference.x) / cube_size;
                        }
                    }
					
                  gp->tex_coords[index].x = 0.5 + x / (cube_size * 6.0);
                  gp->tex_coords[index].y = 0.5 - y / (cube_size * 6.0);
                }
              else
                {
                  gp->tex_coords[index].x = 0.5
                    * (1.0 + asin(gp->normals[index].x) / (0.5 * PI));
                  gp->tex_coords[index].y = -0.5
                    * (1.0 + asin(gp->normals[index].y) / (0.5 * PI));
                }
              /* Adjust the texture co-ordinates to from range 0..1 to
               * 0..width or 0..height as appropriate
               */
              gp->tex_coords[index].x *= gp->tex_width[gp->current_texture];
              gp->tex_coords[index].y *= gp->tex_height[gp->current_texture];
            }
        }
    }
    
  /* Update the center of the whole blob */
  add(&gp->blob_velocity, scale (subtract (gp->blob_anchor, gp->blob_center), 1.0 / 80.0));
  add(&gp->blob_velocity, scale (gp->blob_force, 0.01 / gp->num_nodes));

  add(&gp->blob_center, scale(gp->blob_velocity, 0.5));

  gp->blob_velocity = scale(gp->blob_velocity, 0.999);
}

static void
draw_vertex(mirrorblobstruct *gp, int index)
{
  if (do_colour)
    {
      glColor3ub(gp->colours[index].red,
                 gp->colours[index].green,
                 gp->colours[index].blue);
    }
  if (load_textures)
    {
      glTexCoord2fv(&gp->tex_coords[index].x);
    }
  glNormal3fv(&gp->normals[index].x);
  glVertex3fv(&gp->dots[index].x);
}

/******************************************************************************
 *
 * Draw the blob shape.
 *
 */
static void
draw_blob (mirrorblobstruct *gp)
{
  int face;

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
/*  glRotatef(current_device_rotation(), 0, 0, 1); */

  /* Move down the z-axis. */
  glTranslatef (0.0, 0.0, -4.0);

  gltrackball_rotate (gp->trackball);

  /* glColor4ub (255, 0, 0, 128); */
  glBegin(GL_TRIANGLES);
  for (face = 0; face < gp->num_faces; face++)
    {
      draw_vertex(gp, gp->faces[face].node1);
      draw_vertex(gp, gp->faces[face].node2);
      draw_vertex(gp, gp->faces[face].node3);
    }
  glEnd();

#if 0
  glBegin(GL_LINES);
  for (face = 0; face < gp->num_faces; face++)
    {
      if (gp->normals[gp->faces[face].node1].z > 0.0)
        {
          Vector3D end = gp->dots[gp->faces[face].node1];
          glVertex3dv(&end);
          add(&end, scale(gp->normals[gp->faces[face].node1], 0.25));
          glVertex3dv(&end);
        }
    }
  glEnd();
#endif
	
  glLoadIdentity();
}

/******************************************************************************
 *
 * Draw the background image simply map a texture onto a full screen quad.
 */
static void
draw_background (ModeInfo *mi)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
  GLfloat rot = current_device_rotation();
    
  glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
  glEnable (GL_TEXTURE_2D);
  glDisable(GL_LIGHTING);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

  /* Reset the projection matrix to make it easier to get the size of the quad
   * correct
   */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  glRotatef (-rot, 0, 0, 1);
/*
  if ((rot >  45 && rot <  135) ||
      (rot < -45 && rot > -135))
    {
      GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
      glScalef (s, 1/s, 1);
    }
*/

  glOrtho(0.0, MI_WIDTH(mi), MI_HEIGHT(mi), 0.0, -1000.0, 1000.0);

  glBegin (GL_QUADS);
    
  glTexCoord2f (0.0, 0.0);
  glVertex2i (0, 0);
    
  glTexCoord2f (0.0, gp->tex_height[gp->current_texture]);
  glVertex2i (0, MI_HEIGHT(mi));

  glTexCoord2f (gp->tex_width[gp->current_texture], gp->tex_height[gp->current_texture]);
  glVertex2i (MI_WIDTH(mi), MI_HEIGHT(mi));

  glTexCoord2f (gp->tex_width[gp->current_texture], 0.0);
  glVertex2i (MI_WIDTH(mi), 0);
  glEnd();

  glPopMatrix ();
  glMatrixMode (GL_MODELVIEW);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

/******************************************************************************
 *
 * Update the scene.
 */
static GLvoid
draw_scene(ModeInfo * mi)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
    
  double fade = 0.0;
  double current_time;
  check_gl_error ("draw_scene");

  mi->polygon_count = 0;
  glColor4f (1.0, 1.0, 1.0, 1.0);

  current_time = double_time();
  switch (gp->state)
    {
    case INITIALISING:
      glColor4f (0.0, 0.0, 0.0, 1.0);
      fade = 1.0;
      break;

    case TRANSITIONING:
      fade = 1.0 - (current_time - gp->state_start_time) / fade_time;
      break;

    case LOADING: /* FALL-THROUGH */
    case HOLDING:
      fade = 1.0;
      break;
    }

  /* Set the correct texture, when transitioning this ensures that the first draw
   * is the original texture (which has the new texture drawn over it with decreasing
   * transparency)
   */
  if (load_textures)
    {
      glBindTexture(GL_TEXTURE_2D, gp->textures[gp->current_texture]);
    }

  glDisable (GL_DEPTH_TEST);
  if (do_paint_background)
    {
      glEnable (GL_TEXTURE_2D);
      if (motion_blur > 0.0)
        {
          glClear(GL_DEPTH_BUFFER_BIT);
          glEnable (GL_BLEND);
          glColor4f (1.0, 1.0, 1.0, motion_blur);
        }
      else
        {
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
      draw_background (mi);
      mi->polygon_count++;

      /* When transitioning between two images paint the new image over the old
       * image with a varying alpha value to get a smooth fade.
       */
      if (gp->state == TRANSITIONING)
        {
          glEnable (GL_BLEND);
          /* Select the texture to transition to */
          glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]);
          glColor4f (1.0, 1.0, 1.0, 1.0 - fade);

          draw_background (mi);
          mi->polygon_count++;

          /* Select the original texture to draw the blob */
          glBindTexture (GL_TEXTURE_2D, gp->textures[gp->current_texture]);
        }
      /* Clear the depth buffer bit so the backgound is behind the blob */
      glClear(GL_DEPTH_BUFFER_BIT);
    }
  else if (motion_blur > 0.0)
    {
      glEnable (GL_BLEND);
      glColor4f (0.0, 0.0, 0.0, motion_blur);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glTranslatef (0.0, 0.0, -4.0);
      glRectd (-10.0, -10.0, 10.0, 10.0);
      if (wireframe)
        {
          glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }
      glClear(GL_DEPTH_BUFFER_BIT);
    }
  else
    {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

  if (!do_texture)
    {
      fade = 1.0;
      glDisable (GL_TEXTURE_2D);
    }

  calc_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE, 2.5, fade * blend);

  set_blob_gl_state(fade * blend);

  if (blend < 1.0)
    {
      /* Disable the colour chanels so that only the depth buffer is updated */
      glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
      draw_blob(gp);
      mi->polygon_count += gp->num_faces;
      glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    }
	
  glDepthFunc(GL_LEQUAL);
  draw_blob(gp);
  mi->polygon_count += gp->num_faces;

  /* While transitioning between images draw a second blob with a modified
   * alpha value.
   */
  if (load_textures && (hold_time > 0))
    {
      switch (gp->state)
        {
        case INITIALISING:
          if (!gp->waiting_for_image_p)
            {
              gp->state = HOLDING;
            }
          break;
		
        case HOLDING:
          if ((current_time - gp->state_start_time) > hold_time)
            {
              grab_texture(mi, 1 - gp->current_texture);
              gp->state = LOADING;
            }
          break;

        case LOADING:
          /* Once the image has loaded move to the TRANSITIONING STATE */
          if (!gp->waiting_for_image_p)
            {
              gp->state = TRANSITIONING;
              /* Get the time again rather than using the current time so
               * that the time taken by the grab_texture function is not part
               * of the fade time
               */
              gp->state_start_time = double_time();
            }
          break;        

        case TRANSITIONING:

          /* If the blob is textured draw over existing blob to fade between
           * images
           */
          if (do_texture)
            {
              /* Select the texture to transition to */
              glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]);
              glEnable (GL_BLEND);
                
              /* If colour is enabled update the alpha data in the buffer and
               * use that in the blending since the alpha of the incomming
               * verticies will not be correct
               */
              if (do_colour)
                {
                  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
                  glClearColor(0.0, 0.0, 0.0, (1.0 - fade) * blend);
                  glClear(GL_COLOR_BUFFER_BIT);
                  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);                    
                  glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);
                }
              else
                {
                  glColor4f (0.9, 0.9, 1.0, (1.0 - fade) * blend);
                }

              draw_blob (gp);
              mi->polygon_count += gp->num_faces;

              if (do_colour)
                {
                  /* Restore the 'standard' blend functions. */
                  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                }
            }
            
          if ((current_time - gp->state_start_time) > fade_time)
            {
              gp->state = HOLDING;
              gp->state_start_time = current_time;
              gp->current_texture = 1 - gp->current_texture;
            }
          break;

        }
    }
}

/******************************************************************************
 *
 * XMirrorblob screen update entry
 */
ENTRYPOINT void
draw_mirrorblob(ModeInfo * mi)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
  Display    *display = MI_DISPLAY(mi);
  Window      window = MI_WINDOW(mi);

  if (!gp->glx_context)
    return;

  /* Wait for the first image; for subsequent images, load them in the
     background while animating. */
  if (gp->waiting_for_image_p && gp->first_image_p)
    return;

  glXMakeCurrent(display, window, *gp->glx_context);
  draw_scene(mi);
  if (mi->fps_p) do_fps (mi);
  glFinish();
  glXSwapBuffers(display, window);
}

/******************************************************************************
 *
 * XMirrorblob screen resize entry
 */
ENTRYPOINT void
reshape_mirrorblob(ModeInfo *mi, int width, int height)
{
  glViewport( 0, 0, MI_WIDTH(mi), MI_HEIGHT(mi) );
  reset_projection(width, height);
}

/****************************************************************************
 *
 * Handle Mouse events
 */
ENTRYPOINT Bool
mirrorblob_handle_event (ModeInfo * mi, XEvent * event)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN (mi)];

  if (event->xany.type == ButtonPress &&
      event->xbutton.button == Button4)
    {
      zoom *= 1.1;
      return True;
    }
  else if (event->xany.type == ButtonPress &&
           event->xbutton.button == Button5)
    {

      zoom *= 0.9;
      return True;
    }
  else if (gltrackball_event_handler (event, gp->trackball,
                                 MI_WIDTH (mi), MI_HEIGHT (mi),
                                 &gp->button_down))
    {
      return True;
    }
  else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
    {
      gp->state_start_time = 0;
      gp->state = HOLDING;
      return True;
    }

  return False;
}

/******************************************************************************
 *
 * XMirrorblob initialise entry
 */
ENTRYPOINT void
init_mirrorblob(ModeInfo * mi)
{
  int screen = MI_SCREEN(mi);

  mirrorblobstruct *gp;

  MI_INIT(mi, Mirrorblob);
  gp = &Mirrorblob[screen];

  gp->window = MI_WINDOW(mi);
  if ((gp->glx_context = init_GL(mi)) != NULL)
    {
      reshape_mirrorblob(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
      initialize_gl(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
    }
  else
    {
      MI_CLEARWINDOW(mi);
    }
  gp->trackball = gltrackball_init(False);
    
  initialise_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE);
  gp->state = INITIALISING;
  gp->state_start_time = double_time();

  gp->first_image_p = True;
}

/******************************************************************************
 *
 * XMirrorblob cleanup entry
 */
ENTRYPOINT void
free_mirrorblob(ModeInfo * mi)
{
  mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
  int i;

  if (!gp->glx_context) return;
  glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *gp->glx_context);

  if (gp->nodes) free(gp->nodes);
  if (gp->faces) free(gp->faces);
  if (gp->bump_data) free(gp->bump_data);
  if (gp->colours) free(gp->colours);
  if (gp->tex_coords) free(gp->tex_coords);
  if (gp->dots) free(gp->dots);
  if (gp->normals) free(gp->normals);
  if (gp->wall_shape) free(gp->wall_shape);
  if (gp->bump_shape) free(gp->bump_shape);
  if (gp->trackball) gltrackball_free (gp->trackball);

  for (i = 0; i < NUM_TEXTURES; i++)
    if (gp->textures[i]) glDeleteTextures(1, &gp->textures[i]);
}

XSCREENSAVER_MODULE ("MirrorBlob", mirrorblob)

#endif
