http://ftp.nluug.nl/pub/os/Linux/distr/pardusrepo/sources/xscreensaver-5.02.tar.gz
[xscreensaver] / hacks / glx / mirrorblob.c
index c6a5c84d2f9d96870de85d644520ef9c71341e0b..fe6985e138e739063cfe0fc1071e2ac46e5db044 100644 (file)
  * 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
  *
  * The mirrorblob screensaver draws a pulsing blob on the screen.  Options
- * include adding a background (via load_texture_async), texturing the blob,
+ * include adding a background (via screen_to_texture), texturing the blob,
  * making the blob semi-transparent and varying the resolution of the blob
  * tessellation.
  *
@@ -40,7 +41,9 @@
     "*useSHM:              True      \n"
 
 # define refresh_mirrorblob 0
+/*
 # define mirrorblob_handle_event 0
+*/
 # include "xlockmore.h"    /* from the xmirrorblob distribution */
 #else /* !STANDALONE */
 # include "xlock.h"        /* from the xlockmore distribution */
 #define DEF_DELAY            "10000"
 #define DEF_FPS              "False"
 #define DEF_WIRE             "False"
-#define DEF_BLEND            "False"
+#define DEF_BLEND            "1.0"
 #define DEF_FOG              "False"
 #define DEF_ANTIALIAS        "False"
-#define DEF_WALLS            "True"
+#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_X_RES            "60"
-#define DEF_Y_RES            "32"
-#define DEF_FIELD_POINTS     "5"
-#define DEF_MOTION_BLUR      "0"
+#define DEF_RESOLUTION       "30"
+#define DEF_BUMPS            "10"
+#define DEF_MOTION_BLUR      "0.0"
 #define DEF_INCREMENTAL      "0"
-#define DEF_HOLD_TIME        "30"
-#define DEF_FADE_TIME        "5"
+#define DEF_HOLD_TIME        "30.0"
+#define DEF_FADE_TIME        "5.0"
+#define DEF_ZOOM             "1.0"
 
 #ifdef HAVE_XMU
 # ifndef VMS
@@ -76,6 +80,7 @@
 # endif /* VMS */
 #endif
 
+#include "gltrackball.h"
 #include "grab-ximage.h"
 
 #undef countof
 
 #define PI  3.1415926535897
 
-/* */
-static int do_wire;
-static int do_blend;
-static int do_fog;
-static int do_antialias;
-static int do_walls;
-static int do_texture;
-static int do_paint_background;
-static int do_colour;
-static int offset_texture;
-static int x_resolution;
-static int y_resolution;
-static int field_points;
-static int motion_blur;
-static int incremental;
-static int fade_time;
-static int hold_time;
+/* 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[] = {
-    {"-wireframe",        ".blob.wire",             XrmoptionNoArg, "true" },
-    {"+wireframe",        ".blob.wire",             XrmoptionNoArg, "false" },
-    {"-blend",            ".blob.blend",            XrmoptionNoArg, "true" },
-    {"+blend",            ".blob.blend",            XrmoptionNoArg, "false" },
+    {"-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" },
@@ -116,22 +123,21 @@ static XrmOptionDescRec opts[] = {
     {"+texture",          ".blob.texture",          XrmoptionNoArg, "false" },
     {"-colour",           ".blob.colour",           XrmoptionNoArg, "true" },
     {"+colour",           ".blob.colour",           XrmoptionNoArg, "false" },
-    {"-offset_texture",   ".blob.offset_texture",   XrmoptionNoArg, "true" },
-    {"+offset_texture",   ".blob.offset_texture",   XrmoptionNoArg, "false" },
-    {"-paint_background", ".blob.paint_background", XrmoptionNoArg, "true" },
-    {"+paint_background", ".blob.paint_background", XrmoptionNoArg, "false" },
-    {"-x_res",            ".blob.x_res",            XrmoptionSepArg, NULL },
-    {"-y_res",            ".blob.y_res",            XrmoptionSepArg, NULL },
-    {"-field_points",     ".blob.field_points",     XrmoptionSepArg, NULL },
-    {"-motion_blur",      ".blob.motion_blur",      XrmoptionSepArg, NULL },
-    {"-incremental",      ".blob.incremental",      XrmoptionSepArg, NULL },
-    {"-fade_time",        ".blob.fade_time",        XrmoptionSepArg, NULL },
-    {"-hold_time",        ".blob.hold_time",        XrmoptionSepArg, NULL },
+    {"-offset-texture",   ".blob.offset_texture",   XrmoptionNoArg, "true" },
+    {"+offset-texture",   ".blob.offset_texture",   XrmoptionNoArg, "false" },
+    {"-paint-background", ".blob.paint_background", XrmoptionNoArg, "true" },
+    {"+paint-background", ".blob.paint_background", XrmoptionNoArg, "false" },
+    {"-resolution",       ".blob.resolution",       XrmoptionSepArg, NULL },
+    {"-bumps",            ".blob.bumps",            XrmoptionSepArg, NULL },
+    {"-motion-blur",      ".blob.motion_blur",      XrmoptionSepArg, 0 },
+    {"-fade-time",        ".blob.fade_time",        XrmoptionSepArg, 0 },
+    {"-hold-time",        ".blob.hold_time",        XrmoptionSepArg, 0 },
+    {"-zoom",             ".blob.zoom",             XrmoptionSepArg, 0 },
 };
 
 static argtype vars[] = {
-    {&do_wire,      "wire",         "Wire",      DEF_WIRE,      t_Bool},
-    {&do_blend,     "blend",        "Blend",     DEF_BLEND,     t_Bool},
+    {&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},
@@ -139,13 +145,12 @@ static argtype vars[] = {
     {&do_colour,    "colour",       "Colour",    DEF_COLOUR,   t_Bool},
     {&offset_texture, "offset_texture","Offset_Texture", DEF_OFFSET_TEXTURE, t_Bool},
     {&do_paint_background,"paint_background","Paint_Background", DEF_PAINT_BACKGROUND, t_Bool},
-    {&x_resolution, "x_res",        "X_Res",        DEF_X_RES,        t_Int},
-    {&y_resolution, "y_res",        "Y_Res",        DEF_Y_RES,        t_Int},
-    {&field_points, "field_points", "Field_Points", DEF_FIELD_POINTS, t_Int},
-    {&motion_blur,  "motion_blur",  "Motion_Blur",  DEF_MOTION_BLUR,  t_Int},
-    {&incremental,  "incremental",  "Incremental",  DEF_INCREMENTAL,  t_Int},
-    {&fade_time,    "fade_time",    "Fade_Time",    DEF_FADE_TIME,    t_Int},
-    {&hold_time,    "hold_time",    "Hold_Time",    DEF_HOLD_TIME,    t_Int},
+    {&resolution,   "resolution",   "Resolution",   DEF_RESOLUTION,   t_Int},
+    {&bumps,        "bumps",        "Bump",         DEF_BUMPS, t_Int},
+    {&motion_blur,  "motion_blur",  "Motion_Blur",  DEF_MOTION_BLUR,  t_Float},
+    {&fade_time,    "fade_time",    "Fade_Time",    DEF_FADE_TIME,    t_Float},
+    {&hold_time,    "hold_time",    "Hold_Time",    DEF_HOLD_TIME,    t_Float},
+    {&zoom,         "zoom",         "Zoom",         DEF_ZOOM,         t_Float},
 };
 
 
@@ -160,11 +165,9 @@ static OptionStruct desc[] =
     {"-/+ colour", "whether to colour the blob"},
     {"-/+ offset_texture", "whether to offset texture co-ordinates"},
     {"-/+ paint_background", "whether to display a background texture (slower)"},
-    {"-x_res", "Blob resolution in x direction"},
-    {"-y_res", "Blob resolution in y direction"},
-    {"-field_points", "Number of field points used to disturb blob"},
+    {"-resolution", "Resolution of blob tesselation"},
+    {"-bumps", "Number of bumps used to disturb blob"},
     {"-motion_blur", "Fade blob images (higher number = faster fade)"},
-    {"-incremental", "Field summation method"},
     {"-fade_time", "Number of frames to transistion to next image"},
     {"-hold_time", "Number of frames before next image"},
 };
@@ -174,12 +177,11 @@ ENTRYPOINT ModeSpecOpt mirrorblob_opts = {countof(opts), opts, countof(vars), va
 #ifdef USE_MODULES
 ModStruct   mirrorblob_description =
 {"mirrorblob", "init_mirrorblob", "draw_mirrorblob", "release_mirrorblob",
- "draw_mirrorblob", "init_mirrorblob", NULL, &mirrorblob_opts,
+ "draw_mirrorblob", "init_mirrorblob", "handle_event", &mirrorblob_opts,
  1000, 1, 2, 1, 4, 1.0, "",
  "OpenGL mirrorblob", 0, NULL};
 #endif
 
-#define NUM_TEXTURES 2
 
 /*****************************************************************************
  * Types used in blob code
@@ -195,46 +197,83 @@ typedef struct
     GLdouble x, y, z;
 } Vector3D;
 
+typedef struct
+{
+    GLdouble w;
+    GLdouble x;
+    GLdouble y;
+    GLdouble z;
+} Quaternion;
+
 typedef struct
 {
     GLubyte red, green, blue, alpha;
 } Colour;
 
-/* Data used for sphere tessellation */
 typedef struct
 {
-    double cosyd, sinyd;
+    Vector3D initial_position;
+    Vector3D position;
+    Vector3D normal;
+} Node_Data;
 
-    /* Number of x points at each row of the blob */
-    int num_x_points;
-} Row_Data;
+typedef struct
+{
+    int node1, node2, node3;
+    Vector3D normal;
+    double length1, length2, length3;
+} Face_Data;
 
-/* Structure to hold sphere distortion data */
+/* Structure to hold data about bumps used to distortion sphere */
 typedef struct
 {
-    double cx, cy, cpower;
-    double mx, my, mpower;
-    double ax, ay, apower;
-    double vx, vy, vpower;
+    double cx, cy, cpower, csize;
+    double ax, ay, power, size;
+    double mx, my, mpower, msize;
+    double vx, vy, vpower, vsize;
     Vector3D pos;
-} Field_Data;
+} 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 */
+Vector3D tetrahedron[4][3] = {
+    {PPP, MMP, MPM},
+    {PMM, MPM, MMP},
+    {PPP, MPM, PMM},
+    {PPP, PMM, MMP}
+};
+
+/*****************************************************************************
+ * Static blob data
+ *****************************************************************************/
+
+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
 {
-  HOLDING=0,
+  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;
-  double freak, v_freak;
-
-  Row_Data *row_data;
 
   /* Parameters controlling the position of the blob as a whole */
   Vector3D blob_center;
@@ -242,18 +281,22 @@ typedef struct {
   Vector3D blob_velocity;
   Vector3D blob_force;
 
-  /* Count of the total number of points */
-  int num_points;
+  /* 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 field function results */
-  double *field, *wall_field;
+  /* Pointer to the bump function results */
+  double *bump_shape, *wall_shape;
 
-  Field_Data *field_data;
+  Bump_Data *bump_data;
 
   /* Use 2 textures to allow a gradual fade between images */
   int current_texture;
@@ -271,6 +314,9 @@ typedef struct {
   Bool waiting_for_image_p;
   Bool first_image_p;
 
+  trackball_state *trackball;
+  int button_down;
+
 } mirrorblobstruct;
 
 static mirrorblobstruct *Mirrorblob = NULL;
@@ -310,6 +356,182 @@ reset_projection(int width, int height)
     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, GLdouble * transform)
+{
+    GLdouble 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, GLdouble * 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;
+    GLdouble 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;
+}
+
 /****************************************************************************
  *
  * Load a texture.
@@ -367,76 +589,98 @@ grab_texture(ModeInfo *mi, int texture_index)
 
 /******************************************************************************
  *
- * Initialise the data used to calculate the blob shape.
+ * Generate internal parameters based on supplied options the parameters to
+ * ensure they are consistant.
+ */
+static void
+set_parameters(void)
+{
+    /* 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)];
-
-    GLfloat fogColor[4] = { 0.1, 0.1, 0.1, 0.1 };
+    
     /* 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.1f, 0.1f, 0.1f, 1.0f };
-    GLfloat sourceLight0[] = { 1.0f, 1.0f, 1.0f, 1.0f };
-    GLfloat specularLight0[] = { 0.7f, 0.6f, 0.3f, 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[] = {0.0f, -500.0f, 500.0f, 1.0f };
-    GLfloat whiteLight1[] = { 0.1f, 0.1f, 0.1f, 1.0f };
-    GLfloat sourceLight1[] = { 1.0f, 0.3f, 0.3f, 1.0f };
-    GLfloat specularLight1[] = { 0.7f, 0.6f, 0.3f, 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 };
 
-    /* Setup our viewport. */
-    glViewport (0, 0, width, height ); 
+    GLfloat fogColor[4] = { 0.4, 0.4, 0.5, 0.1 };
 
-    glEnable(GL_DEPTH_TEST);
+    /* 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)
     {
-        do_blend = 1;
+        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_wire)
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-    }
-    else
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-    }
-
     if (do_fog)
     {
         glEnable(GL_FOG);
-        glFogi(GL_FOG_MODE, GL_LINEAR);
         glFogfv(GL_FOG_COLOR, fogColor);
-        glFogf(GL_FOG_DENSITY, 0.35);
-        glFogf(GL_FOG_START, 2.0);
-        glFogf(GL_FOG_END, 10.0);
+        glFogf(GL_FOG_DENSITY, 0.50);
+        glFogf(GL_FOG_START, 15.0);
+        glFogf(GL_FOG_END, 30.0);
     }
 
-    /* Our shading model--Gouraud (smooth). */
+    /* Set the shading model to smooth (Gouraud shading). */
     glShadeModel (GL_SMOOTH);
 
-    /* Culling. */
-    glCullFace (GL_BACK);
-    glEnable (GL_CULL_FACE);
-    glEnable (GL_DEPTH_TEST);
-    glFrontFace (GL_CCW);
-
     /* Set the clear color. */
     glClearColor( 0, 0, 0, 0 );
 
-    glViewport( 0, 0, width, height );
-
+    glLightModelfv (GL_LIGHT_MODEL_AMBIENT, ambientLight);
     glLightfv (GL_LIGHT0, GL_AMBIENT, whiteLight0);
     glLightfv (GL_LIGHT0, GL_DIFFUSE, sourceLight0);
     glLightfv (GL_LIGHT0, GL_SPECULAR, specularLight0);
@@ -457,21 +701,28 @@ initialize_gl(ModeInfo *mi, GLsizei width, GLsizei height)
 
     /* Set all materials to have specular reflectivity */
     glMaterialfv (GL_FRONT, GL_SPECULAR, specref);
-    glMateriali (GL_FRONT, GL_SHININESS, 64);
+    glMateriali (GL_FRONT, GL_SHININESS, 32);
 
+    /* Let GL implementation scale normal vectors. */
     glEnable (GL_NORMALIZE);
 
     /* Enable Arrays */
-    if (do_texture)
+    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);
 
-        glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+        if (do_texture)
+        {
+            glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+        }
+        glMatrixMode (GL_TEXTURE);
+        glRotated (180.0, 1.0, 0.0, 0.0);
+        glMatrixMode (GL_MODELVIEW);
     }
 
     if (do_colour)
@@ -487,41 +738,58 @@ initialize_gl(ModeInfo *mi, GLsizei width, GLsizei height)
 
 /******************************************************************************
  *
- * Calculate the normal vector for a plane given three points in the plane.
+ * Initialise the openGL state data.
  */
 static void
-calculate_normal (Vector3D point1,
-                  Vector3D point2,
-                  Vector3D point3,
-                  Vector3D *normal)
+set_blob_gl_state(GLdouble alpha)
 {
-    Vector3D vector1, vector2;
-    double magnitude;
-
-    vector1.x = point2.x - point1.x;
-    vector1.y = point2.y - point1.y;
-    vector1.z = point2.z - point1.z;
-
-    vector2.x = point3.x - point2.x;
-    vector2.y = point3.y - point2.y;
-    vector2.z = point3.z - point2.z;
-
-    (*normal).x = vector1.y * vector2.z - vector1.z * vector2.y;
-    (*normal).y = vector1.z * vector2.x - vector1.x * vector2.z;
-    (*normal).z = vector1.x * vector2.y - vector1.y * vector2.x;
+    if (do_antialias)
+    {
+        glEnable(GL_LINE_SMOOTH);
+        glEnable(GL_POLYGON_SMOOTH);
+    }
 
-    /* Adjust the normal to unit magnitude */
-    magnitude = sqrt ((*normal).x * (*normal).x
-                      + (*normal).y * (*normal).y
-                      + (*normal).z * (*normal).z);
+    if (wireframe)
+    {
+        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+    }
+    else
+    {
+        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    }
 
-    /* Watch out for divide by zero/underflow */
-    if (magnitude > 1e-300)
+    /* 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
     {
-        (*normal).x /= magnitude;
-        (*normal).y /= magnitude;
-        (*normal).z /= magnitude;
+        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. */
+        glColor4d (0.9, 0.9, 1.0, alpha);
+    }
+    else
+    {
+        glDisable(GL_BLEND);
+        glColor4d (0.9, 0.9, 1.0, 1.0);
+    }
+    
+    glEnable(GL_DEPTH_TEST);
+    glEnable(GL_LIGHTING);
 }
 
 /******************************************************************************
@@ -533,52 +801,65 @@ calculate_normal (Vector3D point1,
  */
 static int
 initialise_blob(mirrorblobstruct *gp,
-                int width, int height,
-                int field_array_size)
+                int width,
+                int height,
+                int bump_array_size)
 {
-    /* Loop variables */
-    int x, y, i;
-    double xd;
+     /* Loop variables */    
+    int i, u, v, node, side, face, base, base2 = 0;
+    int nodes_on_edge = resolution;
+    Vector3D node1, node2, result;
 
-    gp->colour_cycle = 0;
+    if (nodes_on_edge < 2)
+        return -1;
 
-    gp->row_data = (Row_Data *) malloc (y_resolution * sizeof (Row_Data));
-    if (!gp->row_data)
+    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 row data buffer\n");
+        fprintf (stderr, "Couldn't allocate gp->nodes buffer\n");
         return -1;
     }
 
-    gp->field_data = (Field_Data *) malloc (field_points * sizeof (Field_Data));
-    if (!gp->field_data)
+    gp->faces = (Face_Data *) malloc (gp->num_faces * sizeof (Face_Data));
+    if (!gp->faces)
     {
-        fprintf(stderr, "Couldn't allocate field data buffer\n");
+        fprintf (stderr, "Couldn't allocate faces data buffer\n");
         return -1;
     }
 
-    gp->field = (double *)malloc(field_array_size * sizeof(double));
-    if (!gp->field)
+    gp->bump_data = (Bump_Data *) malloc (bumps * sizeof (Bump_Data));
+    if (!gp->bump_data)
     {
-        fprintf(stderr, "Couldn't allocate field buffer\n");
+        fprintf(stderr, "Couldn't allocate bump data buffer\n");
         return -1;
     }
 
-    gp->wall_field = (double *)malloc(field_array_size * sizeof(double));
-    if (!gp->wall_field)
+    gp->bump_shape = (double *)malloc(bump_array_size * sizeof(double));
+    if (!gp->bump_shape)
     {
-        fprintf(stderr, "Couldn't allocate wall field buffer\n");
+        fprintf(stderr, "Couldn't allocate bump buffer\n");
         return -1;
     }
 
-    gp->dots = (Vector3D *)malloc(x_resolution * y_resolution * sizeof(Vector3D));
+    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 points buffer\n");
+        fprintf(stderr, "Couldn't allocate nodes buffer\n");
         return -1;
     }
     glVertexPointer (3, GL_DOUBLE, 0, (GLvoid *) gp->dots);
 
-    gp->normals = (Vector3D *)malloc(x_resolution * y_resolution * sizeof(Vector3D));
+    gp->normals = (Vector3D *)malloc(gp->num_nodes * sizeof(Vector3D));
     if (!gp->normals)
     {
         fprintf(stderr, "Couldn't allocate normals buffer\n");
@@ -588,7 +869,7 @@ initialise_blob(mirrorblobstruct *gp,
 
     if (do_colour)
     {
-        gp->colours = (Colour *)malloc(x_resolution * y_resolution * sizeof(Colour));
+        gp->colours = (Colour *)malloc(gp->num_nodes * sizeof(Colour));
         if (!gp->colours)
         {
             fprintf(stderr, "Couldn't allocate colours buffer\n");
@@ -599,89 +880,262 @@ initialise_blob(mirrorblobstruct *gp,
 
     if (do_texture)
     {
-        gp->tex_coords = (Vector2D *)malloc(x_resolution * y_resolution
-                                        * sizeof(Vector2D));
+        gp->tex_coords = (Vector2D *)malloc(gp->num_nodes * sizeof(Vector2D));
         if (!gp->tex_coords)
         {
-            fprintf(stderr, "Couldn't allocate tex_coords buffer\n");
+            fprintf(stderr, "Couldn't allocate gp->tex_coords buffer\n");
             return -1;
         }
         glTexCoordPointer (2, GL_DOUBLE, 0, (GLvoid *) gp->tex_coords);
     }
 
-    gp->num_points = 0;
-    /* Generate constant row data and count of total number of points */
-    for (y = 0; y < y_resolution; y++)
-    {
-        gp->row_data[y].cosyd = cos(PI * (double)(y * (y_resolution + 1))
-                                / (double)(y_resolution * y_resolution));
-        gp->row_data[y].sinyd = sin(PI * (double)(y * (y_resolution + 1))
-                                / (double)(y_resolution * y_resolution));
-        gp->row_data[y].num_x_points = (int)(x_resolution * gp->row_data[y].sinyd + 1.0);
-        gp->num_points += gp->row_data[y].num_x_points;
-    }
-
-    /* Initialise field data */
-    for (i = 0; i < field_points; i++)
+    /* Initialise bump data */
+    for (i = 0; i < bumps; i++)
     {
-        gp->field_data[i].ax = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
-        gp->field_data[i].ay = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
-        gp->field_data[i].apower = (((double)random() / (double)RAND_MAX) - 0.5);
-
-        gp->field_data[i].pos.x = 1.5 * sin(PI * gp->field_data[i].ay)
-            * cos(PI *  gp->field_data[i].ax);
-        gp->field_data[i].pos.y = 1.5 * cos(PI * gp->field_data[i].ay);
-        gp->field_data[i].pos.z = 1.5 * sin(PI * gp->field_data[i].ay)
-            * sin(PI *  gp->field_data[i].ax);
-
-        gp->field_data[i].cx = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
-        gp->field_data[i].cy = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5);
-        gp->field_data[i].cpower = (((double)random() / (double)RAND_MAX) - 0.5);
-
-        gp->field_data[i].vx = 0.0;
-        gp->field_data[i].vy = 0.0;
-        gp->field_data[i].vpower = 0.0;
-
-        gp->field_data[i].mx = 0.003 * ((double)random() / (double)RAND_MAX);
-        gp->field_data[i].my = 0.003 * ((double)random() / (double)RAND_MAX);
-        gp->field_data[i].mpower = 0.003 * ((double)random() / (double)RAND_MAX);
+        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 field strength */
-    for (i = 0; i < field_array_size; i++)
+    /* Initialise lookup table of bump strength */
+    for (i = 0; i < bump_array_size; i++)
     {
-        xd = 2.0 * (((double)i / (double)field_array_size));
+        double xd, xd2;
+        xd = i / (double)bump_array_size;
 
-        xd = 3.0 * xd * xd * xd * xd;
-        gp->field[i] = 0.4 / (field_points * (xd + 0.1));
+        xd2 = 48.0 * xd * xd;
+        gp->bump_shape[i] = 0.1 / (xd2 + 0.1);
 
-        xd = 10.0 * (((double)i / (double)field_array_size));
-        gp->wall_field[i] = 0.4 / (xd * xd * xd * xd + 1.0);
+        xd2 = 40.0 * xd * xd * xd * xd;
+        gp->wall_shape[i] = 0.4 / (xd2 + 0.1);
     }
 
-    for (y = 0; y < y_resolution; y++)
+    node = 0;
+    face = 0;
+    for (side = 0; side < 4; side++)
     {
-        for (x = 0; x < gp->row_data[y].num_x_points; x++)
+        base = node;
+        if (side == 2) 
         {
-            i = x + y * x_resolution;
-            xd = 2.0 * (((double)x / (double)gp->row_data[y].num_x_points) - 0.5);
-
-            gp->dots[i].x = gp->row_data[y].sinyd * cos(PI * xd);
-            gp->dots[i].y = gp->row_data[y].cosyd;
-            gp->dots[i].z = gp->row_data[y].sinyd * sin(PI * xd);
-            gp->normals[i].x = gp->row_data[y].sinyd * cos(PI * xd);
-            gp->normals[i].y = gp->row_data[y].cosyd;
-            gp->normals[i].z = gp->row_data[y].sinyd * sin(PI * xd);
-            if (do_texture)
+            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++)
             {
-                gp->tex_coords[i].x = 2.0 - 2.0 * x / (float) gp->row_data[y].num_x_points;
-                gp->tex_coords[i].y = 1.0 - y / (float) y_resolution;
+                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
+ */
+static inline double
+length (Vector3D u)
+{
+    return sqrt (u.x * u.x + u.y * u.y + u.z * u.z);
+}
 
 /******************************************************************************
  *
@@ -689,398 +1143,234 @@ initialise_blob(mirrorblobstruct *gp,
  */
 static void
 calc_blob(mirrorblobstruct *gp,
-          int width, int height,
-          int field_array_size,
+          int width,
+          int height,
+          int bump_array_size,
           float limit,
           double fade)
 {
     /* Loop variables */
-    int x, y, i, index, index1, index2, index3;
-    /* position of a point */
-    double xd, yd, zd, offset_x, offset_y, offset_z;
-    double strength, radius;
-    double xdist, ydist, zdist;
+    int i, index, face;
+    /* position of a node */
+    Vector3D node;
+    Vector3D offset;
+    Vector3D bump_vector;
     int dist;
 
-    /* Color components */
-
-    gp->colour_cycle++;
-
-    /* Update position and strength of points used to distort the blob */
-    for (i = 0; i < field_points; i++)
+    /* Update position and strength of bumps used to distort the blob */
+    for (i = 0; i < bumps; i++)
     {
-        gp->field_data[i].vx += gp->field_data[i].mx*(gp->field_data[i].cx - gp->field_data[i].ax);
-        gp->field_data[i].vy += gp->field_data[i].my*(gp->field_data[i].cy - gp->field_data[i].ay);
-        gp->field_data[i].vpower += gp->field_data[i].mpower
-            * (gp->field_data[i].cpower - gp->field_data[i].apower);
-
-        gp->field_data[i].ax += 0.1 * gp->field_data[i].vx;
-        gp->field_data[i].ay += 0.1 * gp->field_data[i].vy;
-        gp->field_data[i].apower += 0.1 * gp->field_data[i].vpower;
-
-        gp->field_data[i].pos.x = 1.0 * sin(PI * gp->field_data[i].ay)
-            * cos(PI * gp->field_data[i].ax);
-        gp->field_data[i].pos.y = 1.0 * cos(PI * gp->field_data[i].ay);
-        gp->field_data[i].pos.z = 1.0 * sin(PI * gp->field_data[i].ay)
-            * sin(PI * gp->field_data[i].ax);
+        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);
     }
 
-    gp->blob_force.x = 0.0;
-    gp->blob_force.y = 0.0;
-    gp->blob_force.z = 0.0;
-    for (y = 0; y < y_resolution; y++)
+    /* 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)
     {
-        for (x = 0; x < gp->row_data[y].num_x_points; x++)
-        {
-            index = x + y * x_resolution;
-            xd = 2.0 * PI * (((double)x / (double)gp->row_data[y].num_x_points) - 0.5);
-
-            radius = 1.0 + 0.0 * sin (xd * 10);
+        node = gp->nodes[index].initial_position;
+        gp->nodes[index].normal = zero_vector;
 
-            zd = radius * gp->row_data[y].sinyd * sin(xd);
-            xd = radius * gp->row_data[y].sinyd * cos(xd);
-            yd = radius * gp->row_data[y].cosyd;
+        offset = zero_vector;
+        for ( i = 0; i < bumps; i++)
+        {
+            bump_vector = subtract(gp->bump_data[i].pos, node);
 
-            gp->normals[index].x = xd;
-            gp->normals[index].y = yd;
-            gp->normals[index].z = zd;
+            dist = bump_array_size * dot(bump_vector, bump_vector) * gp->bump_data[i].size;
 
-            offset_x = 0.0;
-            offset_y = 0.0;
-            offset_z = 0.0;
-            strength = 0.0;
-            for ( i = 0; i < field_points; i++)
+            if (dist < bump_array_size)
             {
-                xdist = gp->field_data[i].pos.x - xd;
-                ydist = gp->field_data[i].pos.y - yd;
-                zdist = gp->field_data[i].pos.z - zd;
-                dist = field_array_size * (xdist * xdist + ydist * ydist
-                                           + zdist * zdist) * 0.1;
-
-                strength += PI * gp->field_data[i].apower;
-
-                if (dist < field_array_size)
-                {
-                    offset_x += xd * gp->field_data[i].apower * gp->field[dist];
-                    offset_y += yd * gp->field_data[i].apower * gp->field[dist];
-                    offset_z += zd * gp->field_data[i].apower * gp->field[dist];
-
-                    gp->blob_force.x += 1.0 * xd * gp->field_data[i].apower * gp->field[dist];
-                    gp->blob_force.y += 1.0 * yd * gp->field_data[i].apower * gp->field[dist];
-                    gp->blob_force.z += 1.0 * zd * gp->field_data[i].apower * gp->field[dist];
+                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]));
+            }
+        }
 
-                    strength *= 2.0 * gp->field[dist];
-                }
+        add(&node, offset);
+        node = scale(node, zoom);
+        add(&node, gp->blob_center);
 
-                if (incremental)
-                {
-                    xd += offset_x * gp->freak * gp->freak;
-                    yd += offset_y * gp->freak * gp->freak;
-                    zd += offset_z * gp->freak * gp->freak;
-                }
-                if (incremental == 1)
-                {
-                    offset_x = 0.0;
-                    offset_y = 0.0;
-                    offset_z = 0.0;
-                }
-            }
+        if (do_walls)
+        {
+            if (node.z < -limit) node.z = -limit;
+            if (node.z > limit) node.z = limit;
 
-            if (incremental < 3)
+            dist = bump_array_size * (node.z + limit) * (node.z + limit) * 0.5;
+            if (dist < bump_array_size)
             {
-                xd += offset_x;
-                yd += offset_y;
-                zd += offset_z;
+                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);
             }
-            xd += gp->blob_center.x;
-            yd += gp->blob_center.y;
-            zd += gp->blob_center.z;
-
-            if (do_colour)
+            else
             {
-                gp->colours[index].red = 128 + (int)(sin(strength + gp->colour_cycle * 0.01 + 2.0 * PI * x / gp->row_data[y].num_x_points) * 127.0);
-                gp->colours[index].green = 128 + (int)(cos(strength + gp->colour_cycle * 0.025) * 127.0);
-                gp->colours[index].blue = 128 + (int)(sin(strength + gp->colour_cycle * 0.03 + 2.0 * PI * y / y_resolution) * 127.0);
-                gp->colours[index].alpha = (int)(255.0 * fade);
-            }
+                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);
+                }
 
-            /* Add walls */
-            if (do_walls)
-            {
-                if (zd < -limit) zd = -limit;
-                if (zd > limit) zd = limit;
+                if (node.y < -limit) node.y = -limit;
+                if (node.y > limit) node.y = limit;
 
-                dist = field_array_size * (zd + limit) * (zd + limit) * 0.5;
-                if (dist < field_array_size)
+                dist = bump_array_size * (node.y + limit) * (node.y + limit) * 0.5;
+                if (dist < bump_array_size)
                 {
-                    xd += (xd - gp->blob_center.x) * gp->wall_field[dist];
-                    yd += (yd - gp->blob_center.y) * gp->wall_field[dist];
-                    gp->blob_force.z += (zd + limit);
+                    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 = field_array_size * (zd - limit) * (zd - limit) * 0.5;
-                    if (dist < field_array_size)
-                    {
-                        xd += (xd - gp->blob_center.x) * gp->wall_field[dist];
-                        yd += (yd - gp->blob_center.y) * gp->wall_field[dist];
-                        gp->blob_force.z -= (zd - limit);
-                    }
-
-                    if (yd < -limit) yd = -limit;
-                    if (yd > limit) yd = limit;
-
-                    dist = field_array_size * (yd + limit) * (yd + limit) * 0.5;
-                    if (dist < field_array_size)
+                    dist = bump_array_size * (node.y - limit) * (node.y - limit) * 0.5;
+                    if (dist < bump_array_size)
                     {
-                        xd += (xd - gp->blob_center.x) * gp->wall_field[dist];
-                        zd += (zd - gp->blob_center.z) * gp->wall_field[dist];
-                        gp->blob_force.y += (yd + limit);
-                    }
-                    else
-                    {
-                        dist = field_array_size * (yd - limit) * (yd - limit) * 0.5;
-                        if (dist < field_array_size)
-                        {
-                            xd += (xd - gp->blob_center.x) * gp->wall_field[dist];
-                            zd += (zd - gp->blob_center.z) * gp->wall_field[dist];
-                            gp->blob_force.y -= (yd - limit);
-                        }
+                        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 (xd < -limit) xd = -limit;
-                    if (xd > limit) xd = limit;
+                if (node.x < -limit) node.x = -limit;
+                if (node.x > limit) node.x = limit;
 
-                    dist = field_array_size * (xd + limit) * (xd + limit) * 0.5;
-                    if (dist < field_array_size)
-                    {
-                        yd += (yd - gp->blob_center.y) * gp->wall_field[dist];
-                        zd += (zd - gp->blob_center.z) * gp->wall_field[dist];
-                        gp->blob_force.x += (xd + 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);
+                }
+                else
+                {
+                    dist = bump_array_size * (node.x - limit) * (node.x - limit) * 0.5;
+                    if (dist < bump_array_size)
                     {
-                        dist = field_array_size * (xd - limit) * (xd - limit) * 0.5;
-                        if (dist < field_array_size)
-                        {
-                            yd += (yd - gp->blob_center.y) * gp->wall_field[dist];
-                            zd += (zd - gp->blob_center.z) * gp->wall_field[dist];
-                            gp->blob_force.x -= (xd - limit);
-                        }
+                        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 (yd < -limit) yd = -limit;
-                    if (yd > limit) yd = limit;
                 }
-            }
 
-            gp->dots[index].x = xd;
-            gp->dots[index].y = yd;
-            gp->dots[index].z = zd;
+                if (node.y < -limit) node.y = -limit;
+                if (node.y > limit) node.y = limit;
+            }
         }
+        gp->dots[index] = node;
     }
 
-    /* Calculate the normals for each vertex and the texture mapping if required.
-     * Although the code actually calculates the normal for one of the triangles
-     * attached to a vertex rather than the vertex itself the results are not too
-     * bad for with a reasonable number of verticies.
-     */
-
-    /* The first point is treated as a special case since the loop expects to use
-     * points in the previous row to form the triangle.
-     */
-    index1 = 0;
-    index2 = y * x_resolution;
-    index3 = 1 + y * x_resolution;
-    calculate_normal (gp->dots[index1], gp->dots[index2], gp->dots[index3], &gp->normals[index1]);
-    if (do_texture)
+    /* Determine the normal for each face */
+    for (face = 0; face < gp->num_faces; face++)
     {
-        if (offset_texture)
-        {
-            gp->tex_coords[index1].x = gp->dots[index1].x * 0.125 + 0.5
-                * (1.0 + 0.25 * asin(gp->normals[index1].x) / (0.5 * PI));
-            gp->tex_coords[index1].y = gp->dots[index1].y * 0.125 + 0.5
-                * (1.0 + 0.25 * asin(gp->normals[index1].y) / (0.5 * PI));
-        }
-        else
-        {
-            gp->tex_coords[index1].x = 0.5 * (1.0 + (asin(gp->normals[index1].x)
-                                                 / (0.5 * PI)));
-            gp->tex_coords[index1].y = 0.5 * (1.0 + (asin(gp->normals[index1].y)
-                                                 / (0.5 * PI)));
-        }
-        gp->tex_coords[index1].x *= gp->tex_width[gp->current_texture];
-        gp->tex_coords[index1].y *= gp->tex_height[gp->current_texture];
+        /* Use nodeers to indexed nodes to help readability */
+        Node_Data *node1 = &gp->nodes[gp->faces[face].node1];
+        Node_Data *node2 = &gp->nodes[gp->faces[face].node2];
+        Node_Data *node3 = &gp->nodes[gp->faces[face].node3];
+
+        gp->faces[face].normal = cross(subtract(node2->position, node1->position),
+                                       subtract(node3->position, node1->position));
+            
+        /* Add the normal for the face onto the normal for the verticies of
+           the face */
+        add(&node1->normal, gp->faces[face].normal);
+        add(&node2->normal, gp->faces[face].normal);
+        add(&node3->normal, gp->faces[face].normal);
     }
 
-    for (y = 1; y < y_resolution - 1; y++)
+    /* Use the normal to set the colour and texture */
+    if (do_colour || do_texture)
     {
-        if (gp->row_data[y - 1].num_x_points)
+        for (index = 0; index < gp->num_nodes; ++index)
         {
-            for (x = 0; x < gp->row_data[y].num_x_points; x++)
+            gp->normals[index] = normalise(gp->nodes[index].normal);
+   
+            if (do_colour)
             {
-                if (x == gp->row_data[y].num_x_points - 1)
+                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)
                 {
-                    index1 = y * x_resolution;
+                    gp->tex_coords[index].x = gp->dots[index].x * 0.125 + 0.5
+                        * (1.0 + 0.25 * asin(gp->normals[index].x) / (0.5 * PI));
+                    gp->tex_coords[index].y = -gp->dots[index].y * 0.125 - 0.5
+                        * (1.0 + 0.25 * asin(gp->normals[index].y) / (0.5 * PI));
                 }
                 else
                 {
-                    index1 = x + 1 + y * x_resolution;
-                }
-                index2 = x + y * x_resolution;
-                index3 = ((x + 0.5) * gp->row_data[y - 1].num_x_points
-                          / gp->row_data[y].num_x_points) + (y - 1) * x_resolution;
-                calculate_normal (gp->dots[index1], gp->dots[index2], gp->dots[index3],
-                                  &gp->normals[index1]);
-                if (do_texture)
-                {
-                    if (offset_texture)
-                    {
-                        gp->tex_coords[index1].x = gp->dots[index1].x * 0.125 + 0.5
-                            * (1.0 + 0.25 * asin(gp->normals[index1].x) / (0.5 * PI));
-                        gp->tex_coords[index1].y = gp->dots[index1].y * 0.125 + 0.5
-                            * (1.0 + 0.25 * asin(gp->normals[index1].y) / (0.5 * PI));
-                    }
-                    else
-                    {
-                        gp->tex_coords[index1].x = 0.5 * (1.0 + (asin(gp->normals[index1].x)
-                                                             / (0.5 * PI)));
-                        gp->tex_coords[index1].y = 0.5 * (1.0 + (asin(gp->normals[index1].y)
-                                                             / (0.5 * PI)));
-                    }
-                    gp->tex_coords[index1].x *= gp->tex_width[gp->current_texture];
-                    gp->tex_coords[index1].y *= gp->tex_height[gp->current_texture];
+                    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];
             }
         }
     }
-    index1 = (y_resolution - 1) * x_resolution;
-    index2 = (y_resolution - 2) * x_resolution;
-    index3 = 1 + (y_resolution - 2) * x_resolution;
-    calculate_normal (gp->dots[index1], gp->dots[index2], gp->dots[index3], &gp->normals[index1]);
-    if (do_texture)
-    {
-        if (offset_texture)
-        {
-            gp->tex_coords[index1].x = gp->dots[index1].x * 0.125 + 0.5
-                * (1.0 + 0.25 * asin(gp->normals[index1].x) / (0.5 * PI));
-            gp->tex_coords[index1].y = gp->dots[index1].y * 0.125 + 0.5
-                * (1.0 + 0.25 * asin(gp->normals[index1].y) / (0.5 * PI));
-        }
-        else
-        {
-            gp->tex_coords[index1].x = 0.5 * (1.0 + (asin(gp->normals[index1].x)
-                                                 / (0.5 * PI)));
-            gp->tex_coords[index1].y = 0.5 * (1.0 + (asin(gp->normals[index1].y)
-                                                 / (0.5 * PI)));
-        }
-        gp->tex_coords[index1].x *= gp->tex_width[gp->current_texture];
-        gp->tex_coords[index1].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));
 
-    gp->freak += gp->v_freak;
-    gp->v_freak += -gp->freak / 2000000.0;
+    add(&gp->blob_center, scale(gp->blob_velocity, 0.5));
 
-    /* Update the center of the whole blob */
-    gp->blob_velocity.x += (gp->blob_anchor.x - gp->blob_center.x) / 80.0
-        + 0.01 * gp->blob_force.x / gp->num_points;
-    gp->blob_velocity.y += (gp->blob_anchor.y - gp->blob_center.y) / 80.0
-        + 0.01 * gp->blob_force.y / gp->num_points;
-    gp->blob_velocity.z += (gp->blob_anchor.z - gp->blob_center.z) / 80.0
-        + 0.01 * gp->blob_force.z / gp->num_points;
-
-    gp->blob_center.x += gp->blob_velocity.x * 0.5;
-    gp->blob_center.y += gp->blob_velocity.y * 0.5;
-    gp->blob_center.z += gp->blob_velocity.z * 0.5;
-
-    gp->blob_velocity.x *= 0.99;
-    gp->blob_velocity.y *= 0.99;
-    gp->blob_velocity.z *= 0.99;
+    gp->blob_velocity = scale(gp->blob_velocity, 0.999);
 }
 
 /******************************************************************************
  *
  * Draw the blob shape.
  *
- * The horrendous indexing to calculate the verticies that form a particular
- * traiangle is the result of the conversion of my first non-openGL version of
- * blob to this openGL version.  This may be tidied up when I finally playing
- * with the more interesting bits of the code.
  */
 static void
 draw_blob (mirrorblobstruct *gp)
 {
-    int x, y, x2, x3;
-    int index1, index2, index3;
-    int lower, upper;
+    int face;
 
     glMatrixMode (GL_MODELVIEW);
     glLoadIdentity ();
 
     /* Move down the z-axis. */
-    glTranslatef (0.0, 0.0, -5.0 );
+    glTranslatef (0.0, 0.0, -4.0);
 
-    for (y = 1; y < y_resolution; y++)
-    {
-        if (gp->row_data[y - 1].num_x_points)
-        {
-            for (x = 0; x < gp->row_data[y].num_x_points; x++)
-            {
-                glBegin (GL_TRIANGLES);
-                if (x == gp->row_data[y].num_x_points - 1)
-                {
-                    index1 = y * x_resolution;
-                }
-                else
-                {
-                    index1 = x + 1 + y * x_resolution;
-                }
-                index2 = x + y * x_resolution;
-                index3 = ((x + 0.5) * gp->row_data[y - 1].num_x_points
-                          / gp->row_data[y].num_x_points) + (y - 1) * x_resolution;
-                glArrayElement(index1);
-                glArrayElement(index2);
-                glArrayElement(index3);
-                glEnd();
-
-                lower = ((x - 0.5) * gp->row_data[y - 1].num_x_points
-                         / (float)gp->row_data[y].num_x_points);
-                upper = ((x + 0.5) * gp->row_data[y - 1].num_x_points
-                         / (float)gp->row_data[y].num_x_points);
-
-                if (upper > lower)
-                {
-                    glBegin (GL_TRIANGLE_FAN);
-                    index1 = x + y * x_resolution;
+    gltrackball_rotate (gp->trackball);
 
-                    for (x2 = lower; x2 <= upper; x2++)
-                    {
-                        x3 = x2;
-                        while (x3 < 0) x3 += gp->row_data[y - 1].num_x_points;
-                        while (x3 >= gp->row_data[y - 1].num_x_points)
-                            x3 -= gp->row_data[y - 1].num_x_points;
-                        index2 = x3 + (y - 1) * x_resolution;
-
-                        if (x2 < upper)
-                        {
-                            x3 = x2 + 1;
-                            while (x3 < 0) x3 += gp->row_data[y - 1].num_x_points;
-                            while (x3 >= gp->row_data[y - 1].num_x_points)
-                                x3 -= gp->row_data[y - 1].num_x_points;
-                            index3 = x3 + (y - 1) * x_resolution;
-                            if (x2 == lower)
-                            {
-                                glArrayElement(index1);
-                            }
-                        }
-                        glArrayElement(index2);
-                    }
-                    glEnd ();
-                }
-            }
-        }
+    /* glColor4ub (255, 0, 0, 128); */
+    glBegin (GL_TRIANGLES);
+    for (face = 0; face < gp->num_faces; face++)
+    {
+        glArrayElement (gp->faces[face].node1);
+        glArrayElement (gp->faces[face].node2);
+        glArrayElement (gp->faces[face].node3);
     }
+    glEnd ();
+    glLoadIdentity ();
 }
 
 /******************************************************************************
@@ -1091,10 +1381,11 @@ static void
 draw_background (ModeInfo *mi)
 {
     mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
-
+    
     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
@@ -1110,10 +1401,10 @@ draw_background (ModeInfo *mi)
     glTexCoord2f (0.0, 0.0);
     glVertex2i (0, 0);
     
-    glTexCoord2f (0.0, -gp->tex_height[gp->current_texture]);
+    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]);
+    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);
@@ -1133,7 +1424,7 @@ static GLvoid
 draw_scene(ModeInfo * mi)
 {
     mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)];
-
+    
     double fade = 0.0;
     double current_time;
     check_gl_error ("draw_scene");
@@ -1144,31 +1435,37 @@ draw_scene(ModeInfo * mi)
     switch (gp->state)
     {
     case TRANSITIONING:
-        fade = (current_time - gp->state_start_time) / fade_time;
+        fade = 1.0 - (current_time - gp->state_start_time) / fade_time;
         break;
 
+    case LOADING: /* FALL-THROUGH */
     case HOLDING:
-        fade = 0.0;
+        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 (do_texture)
+    if (load_textures)
     {
-      glBindTexture (GL_TEXTURE_2D, gp->textures[gp->current_texture]);
+        glBindTexture (GL_TEXTURE_2D, gp->textures[gp->current_texture]);
     }
 
-    if (do_paint_background && !do_wire)
+    glDisable (GL_DEPTH_TEST);
+    if (do_paint_background)
     {
-        glClear(GL_DEPTH_BUFFER_BIT);
-        if (motion_blur)
+        glEnable (GL_TEXTURE_2D);
+        if (motion_blur > 0.0)
         {
+            glClear(GL_DEPTH_BUFFER_BIT);
             glEnable (GL_BLEND);
-            glColor4ub (255, 255, 255, motion_blur);
+            glColor4d (1.0, 1.0, 1.0, motion_blur);
+        }
+        else
+        {
+            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         }
         draw_background (mi);
 
@@ -1177,33 +1474,30 @@ draw_scene(ModeInfo * mi)
          */
         if (gp->state == TRANSITIONING)
         {
-            glDisable (GL_DEPTH_TEST);
             glEnable (GL_BLEND);
             /* Select the texture to transition to */
             glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]);
-            glColor4d (1.0, 1.0, 1.0, fade);
+            glColor4d (1.0, 1.0, 1.0, 1.0 - fade);
 
             draw_background (mi);
 
             /* Select the original texture to draw the blob */
             glBindTexture (GL_TEXTURE_2D, gp->textures[gp->current_texture]);
-            glEnable (GL_DEPTH_TEST);
         }
         /* Clear the depth buffer bit so the backgound is behind the blob */
         glClear(GL_DEPTH_BUFFER_BIT);
     }
-    else if (motion_blur)
+    else if (motion_blur > 0.0)
     {
-        glDisable (GL_DEPTH_TEST);
         glEnable (GL_BLEND);
-        glColor4ub (0, 0, 0, motion_blur);
+        glColor4d (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 (do_wire)
+        if (wireframe)
         {
             glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
         }
-        glEnable (GL_DEPTH_TEST);
         glClear (GL_DEPTH_BUFFER_BIT);
     }
     else
@@ -1211,69 +1505,46 @@ draw_scene(ModeInfo * mi)
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     }
 
-    if (do_blend)
+    if (!do_texture)
     {
-        fade = fade * 0.5;
+        fade = 1.0;
+        glDisable (GL_TEXTURE_2D);
     }
 
-    calc_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), 1024, 2.5, fade);
+    calc_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE, 2.5, fade * blend);
 
-    glEnable(GL_LIGHTING);
-    glEnable(GL_LIGHT0);
-    glEnable(GL_LIGHT1);
+    set_blob_gl_state(fade * blend);
 
-    if (do_blend)
+    if (blend < 1.0)
     {
-        glEnable (GL_BLEND);
-        if (do_colour)
-        {
-            glBlendFunc (GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
-        }
-        else
-        {
-            glColor4d (1.0, 1.0, 1.0, 0.5 - fade);
-        }
-    }
-    else
-    {
-        glDisable (GL_BLEND);
-        glColor4d (1.0, 1.0, 1.0, 1.0);
+        /* Disable the three colour chanels so that only the depth buffer is updated */
+        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+        draw_blob(gp);
+        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+        glDepthFunc(GL_LEQUAL);
     }
+    glDepthFunc(GL_LEQUAL);
     draw_blob(gp);
 
-    if (do_blend && do_colour)
-    {
-        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    }
-
-    /* While transitioning draw a second blob twice with a modified alpha channel.
-     * The trasitioning state machine is very crude, it simply counts frames
-     * rather than elapsed time but it works.
+    /* While transitioning between images draw a second blob with a modified
+     * alpha value.
      */
-    if (do_texture && (hold_time > 0))
+    if (load_textures && (hold_time > 0))
     {
         switch (gp->state)
         {
-        case TRANSITIONING:
-            glClear(GL_DEPTH_BUFFER_BIT);
-            glEnable (GL_BLEND);
-            /* Select the texture to transition to */
-            glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]);
-            glColor4d (1.0, 1.0, 1.0, fade);
-            draw_blob (gp);
-
-            if ((current_time - gp->state_start_time) > fade_time)
+        case HOLDING:
+            if ((current_time - gp->state_start_time) > hold_time)
             {
-                gp->state = HOLDING;
-                gp->state_start_time = current_time;
-                gp->current_texture = 1 - gp->current_texture;
+                grab_texture(mi, 1 - gp->current_texture);
+                gp->state = LOADING;
             }
             break;
 
-        case HOLDING:
-            if ((current_time - gp->state_start_time) > hold_time)
+        case LOADING:
+            /* Once the image has loaded move to the TRANSITIONING STATE */
+            if (!gp->waiting_for_image_p)
             {
-                grab_texture (mi, 1 - gp->current_texture);
                 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
@@ -1281,7 +1552,53 @@ draw_scene(ModeInfo * mi)
                  */
                 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
+                {
+                    glColor4d(0.9, 0.9, 1.0, (1.0 - fade) * blend);
+                }
+
+                draw_blob (gp);
+
+                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;
+
         }
     }
 }
@@ -1322,6 +1639,51 @@ reshape_mirrorblob(ModeInfo *mi, int width, int height)
     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 == Button1)
+    {
+        gp->button_down = 1;
+        gltrackball_start (gp->trackball, event->xbutton.x,
+                           event->xbutton.y, MI_WIDTH (mi), MI_HEIGHT (mi));
+        return True;
+    }
+    else if (event->xany.type == ButtonRelease &&
+             event->xbutton.button == Button1)
+    {
+        gp->button_down = 0;
+        return True;
+    }
+    else 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 (event->xany.type == MotionNotify && gp->button_down)
+    {
+        gltrackball_track (gp->trackball, event->xmotion.x,
+                           event->xmotion.y, MI_WIDTH (mi), MI_HEIGHT (mi));
+        return True;
+    }
+    return False;
+}
+
 /******************************************************************************
  *
  * XMirrorblob initialise entry
@@ -1353,12 +1715,11 @@ init_mirrorblob(ModeInfo * mi)
     {
         MI_CLEARWINDOW(mi);
     }
-
-    initialise_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), 1024);
+    gp->trackball = gltrackball_init ();
+    
+    initialise_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE);
     gp->state_start_time = double_time();
 
-    gp->freak = 0.0;
-    gp->v_freak = 0.0007;
     gp->first_image_p = True;
 }
 
@@ -1373,13 +1734,14 @@ release_mirrorblob(ModeInfo * mi)
     int i;
     for (i = 0; i < MI_NUM_SCREENS(mi); i++) {
       mirrorblobstruct *gp = &Mirrorblob[i];
-      if (gp->row_data) free(gp->row_data);
-      if (gp->field_data) free(gp->field_data);
+      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->wall_field) free(gp->wall_field);
-      if (gp->field) free(gp->field);
+      if (gp->wall_shape) free(gp->wall_shape);
+      if (gp->bump_shape) free(gp->bump_shape);
     }
 
     free(Mirrorblob);