From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / glx / crumbler.c
diff --git a/hacks/glx/crumbler.c b/hacks/glx/crumbler.c
new file mode 100644 (file)
index 0000000..cf21a94
--- /dev/null
@@ -0,0 +1,822 @@
+/* crumbler, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#define DEFAULTS       "*delay:        30000       \n" \
+                       "*showFPS:      False       \n" \
+                       "*wireframe:    False       \n" \
+                       "*suppressRotationAnimation: True\n" \
+
+# define free_crumbler 0
+# define release_crumbler 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#include "xlockmore.h"
+#include "colors.h"
+#include "rotator.h"
+#include "quickhull.h"
+#include "gltrackball.h"
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+
+#define DEF_SPIN        "True"
+#define DEF_WANDER      "True"
+#define DEF_SPEED       "1.0"
+#define DEF_DENSITY     "1.0"
+#define DEF_FRACTURE    "0"
+
+#undef RANDSIGN
+#define RANDSIGN() ((random() & 1) ? 1 : -1)
+
+typedef struct {
+  qh_vertex_t *verts;          /* interior point cloud */
+  int nverts, onverts;
+  qh_vertex_t min, max;                /* enclosing box */
+  qh_vertex_t mid, vec;
+  int polygon_count;
+  GLuint dlist;
+  int color;
+  int color_shift;
+} chunk;
+
+typedef struct {
+  GLXContext *glx_context;
+  rotator *rot;
+  trackball_state *trackball;
+  enum { IDLE, SPLIT, PAUSE, FLEE, ZOOM } state;
+  GLfloat tick;
+  Bool button_down_p;
+  int nchunks;
+  chunk **chunks;
+  chunk *ghost;
+
+  int ncolors;
+  XColor *colors;
+} crumbler_configuration;
+
+static crumbler_configuration *bps = NULL;
+
+static Bool do_spin;
+static GLfloat speed;
+static GLfloat density;
+static int fracture;
+static Bool do_wander;
+
+static XrmOptionDescRec opts[] = {
+  { "-spin",    ".spin",    XrmoptionNoArg, "True" },
+  { "+spin",    ".spin",    XrmoptionNoArg, "False" },
+  { "-speed",   ".speed",   XrmoptionSepArg, 0 },
+  { "-density", ".density", XrmoptionSepArg, 0 },
+  { "-fracture",".fracture",XrmoptionSepArg, 0 },
+  { "-wander",  ".wander",  XrmoptionNoArg, "True" },
+  { "+wander",  ".wander",  XrmoptionNoArg, "False" }
+};
+
+static argtype vars[] = {
+  {&do_spin,   "spin",    "Spin",    DEF_SPIN,    t_Bool},
+  {&do_wander, "wander",  "Wander",  DEF_WANDER,  t_Bool},
+  {&speed,     "speed",   "Speed",   DEF_SPEED,   t_Float},
+  {&density,   "density", "Density", DEF_DENSITY, t_Float},
+  {&fracture,  "fracture","Fracture",DEF_FRACTURE,t_Int},
+};
+
+ENTRYPOINT ModeSpecOpt crumbler_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Create a roughly spherical cloud of N random points.
+ */
+static void
+make_point_cloud (qh_vertex_t *verts, int nverts)
+{
+  int i = 0;
+  while (i < nverts)
+    {
+      verts[i].x = (0.5 - frand(1.0));
+      verts[i].y = (0.5 - frand(1.0));
+      verts[i].z = (0.5 - frand(1.0));
+      if ((verts[i].x * verts[i].x +
+           verts[i].y * verts[i].y +
+           verts[i].z * verts[i].z)
+          < 0.25)
+        i++;
+    }
+}
+
+
+static chunk *
+make_chunk (void)
+{
+  chunk *c = (chunk *) calloc (1, sizeof(*c));
+  c->dlist = glGenLists (1);
+  c->color_shift = 1 + (random() % 3) * RANDSIGN();
+  return c;
+}
+
+static void
+render_chunk (ModeInfo *mi, chunk *c)
+{
+  int wire = MI_IS_WIREFRAME(mi);
+  int i, j;
+  qh_mesh_t m;
+  GLfloat d;
+
+  c->polygon_count = 0;
+  c->min.x = c->min.y = c->min.z =  999999;
+  c->max.x = c->max.y = c->max.z = -999999;
+  if (c->nverts <= 3)
+    {
+      fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
+      abort();
+    }
+
+  for (i = 0; i < c->nverts; i++)
+    {
+      if (c->verts[i].x < c->min.x) c->min.x = c->verts[i].x;
+      if (c->verts[i].y < c->min.y) c->min.y = c->verts[i].y;
+      if (c->verts[i].z < c->min.z) c->min.z = c->verts[i].z;
+      if (c->verts[i].x > c->max.x) c->max.x = c->verts[i].x;
+      if (c->verts[i].y > c->max.y) c->max.y = c->verts[i].y;
+      if (c->verts[i].z > c->max.z) c->max.z = c->verts[i].z;
+    }
+
+  c->mid.x = (c->max.x + c->min.x) / 2;
+  c->mid.y = (c->max.y + c->min.y) / 2;
+  c->mid.z = (c->max.z + c->min.z) / 2;
+
+  /* midpoint as normalized vector from origin */
+  d = sqrt (c->mid.x * c->mid.x +
+            c->mid.y * c->mid.y +
+            c->mid.z * c->mid.z);
+  c->vec.x = c->mid.x / d;
+  c->vec.y = c->mid.y / d;
+  c->vec.z = c->mid.z / d;
+
+  if (c->nverts <= 3)
+    {
+      fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
+      abort();
+    }
+
+  m = qh_quickhull3d (c->verts, c->nverts);
+
+  glNewList (c->dlist, GL_COMPILE);
+  if (! wire) glBegin (GL_TRIANGLES);
+  for (i = 0, j = 0; i < m.nindices; i += 3, j++)
+    {
+      qh_vertex_t *v0 = &m.vertices[m.indices[i+0]];
+      qh_vertex_t *v1 = &m.vertices[m.indices[i+1]];
+      qh_vertex_t *v2 = &m.vertices[m.indices[i+2]];
+      qh_vec3_t   *n  = &m.normals[m.normalindices[j]];
+
+      if (i+2 >= m.nindices) abort();
+      if (j >= m.nnormals) abort();
+
+      glNormal3f (n->x, n->y, n->z);
+      if (wire) glBegin(GL_LINE_LOOP);
+      glVertex3f (v0->x, v0->y, v0->z);
+      glVertex3f (v1->x, v1->y, v1->z);
+      glVertex3f (v2->x, v2->y, v2->z);
+      if (wire) glEnd();
+      c->polygon_count++;
+    }
+  if (! wire) glEnd();
+
+  if (wire)
+    {
+      glPointSize (1);
+      glColor3f (0, 1, 0);
+      glBegin (GL_POINTS);
+      for (i = 0; i < c->nverts; i++)
+        {
+          if (i > 0 && i == c->onverts)
+            {
+              glEnd();
+              glColor3f (1, 0, 0);
+              glBegin (GL_POINTS);
+            }
+          glVertex3f (c->verts[i].x, c->verts[i].y, c->verts[i].z);
+        }
+      glEnd();
+    }
+
+  glEndList();
+
+  qh_free_mesh (m);
+}
+
+
+static void
+free_chunk (chunk *c)
+{
+  free (c->verts);
+  glDeleteLists (c->dlist, 1);
+  free (c);
+}
+
+
+/* Make sure the chunk contains at least N points.
+   As we subdivide, the number of points is reduced.
+   This adds new points to the interior that do not
+   affect the shape of the outer hull.
+ */
+static void
+pad_chunk (chunk *c, int min)
+{
+  /* Allocate a new array of size N
+     Copy the old points into it
+     while size < N
+       pick two random points
+       add a point that is somewhere along the line between them
+       (that point will still be inside the old hull)
+   */
+  qh_vertex_t *verts;
+  int i;
+  if (c->nverts >= min) return;
+  if (c->nverts <= 3) abort();
+  verts = (qh_vertex_t *) calloc (min, sizeof(*verts));
+  if (!verts) abort();
+  memcpy (verts, c->verts, c->nverts * sizeof(*verts));
+  i = c->nverts;
+  while (i < min)
+    {
+      qh_vertex_t v;
+      int j0, j1;
+      GLfloat r;
+      j0 = random() % c->nverts;
+      do {
+        j1 = random() % c->nverts;
+      } while (j0 == j1);
+
+      r = 0.2 + frand(0.6);
+# undef R
+# define R(F) v.F = c->verts[j0].F + \
+                    r * (fabs (c->verts[j1].F - c->verts[j0].F)) \
+                    * (c->verts[j0].F > c->verts[j1].F ? -1 : 1)
+      R(x);
+      R(y);
+      R(z);
+# undef R
+
+      /* Sometimes quickhull.c is giving us concave and un-closed polygons.
+         Maybe it gets confused if there are duplicate points?  So reject
+         this point if it is within epsilon of any earlier point.
+       */
+# if 0 /* Nope, that's not it. */
+      {
+        Bool ok = True;
+        int j;
+        for (j = 0; j < i; j++)
+          {
+            
+            double X = c->verts[j].x - v.x;
+            double Y = c->verts[j].y - v.y;
+            double Z = c->verts[j].z - v.z;
+            double d2 = X*X + Y*Y + Z*Z;
+            if (d2 < 0.0001)
+              {
+                /* fprintf (stderr, "## REJ %f\n",d2); */
+                ok = False;
+                break;
+              }
+          }
+        if (! ok) continue;
+      }
+# endif
+
+      verts[i++] = v;
+    }
+
+#if 0
+  fprintf (stdout, " int n = %d;\n", min);
+  fprintf (stdout, " qh_vertex_t v[] = {");
+  for (i = 0; i < min; i++)
+    fprintf(stdout,"{%f,%f,%f},", verts[i].x, verts[i].y, verts[i].z);
+  fprintf (stdout, "};\n\n");
+#endif
+
+  free (c->verts);
+  c->verts = verts;
+  c->onverts = c->nverts;
+  c->nverts = min;
+
+#if 0
+  qh_vertex_t *verts2 = (qh_vertex_t *) calloc (n, sizeof(*verts2));
+  memcpy (verts2, v, n * sizeof(*verts2));
+  free (c->verts);
+  c->verts = verts2;
+  c->onverts = 0;
+  c->nverts = n;
+#endif
+}
+
+
+/* Returns a list of N new chunks.
+ */
+static chunk **
+split_chunk (ModeInfo *mi, chunk *c, int nchunks)
+{
+  /* Pick N key-points from the cloud.
+     Create N new chunks.
+     For each old point:
+       It goes in chunk N if it is closest to key-point N.
+     Free old chunk.
+     for each new chunk
+       render_chunk
+   */
+  crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+  chunk **chunks = (chunk **) calloc (nchunks, sizeof(*chunks));
+  int *keys = (int *) calloc (nchunks, sizeof(*keys));
+  int i, j;
+  chunk *c2;
+
+  for (i = 0; i < nchunks; i++)
+    {
+      /* Fill keys with random numbers that are not duplicates. */
+      Bool ok = True;
+      if (nchunks >= c->nverts)
+        {
+          fprintf (stderr, "%s: nverts %d nchunks %d\n", progname,
+                   c->nverts, nchunks);
+          abort();
+        }
+      do {
+        keys[i] = random() % c->nverts;
+        for (j = 0; j < i; j++)
+          if (keys[i] == keys[j])
+            {
+              ok = False;
+              break;
+            }
+        ok = True;
+      } while (!ok);
+
+      c2 = make_chunk();
+      chunks[i] = c2;
+      chunks[i]->nverts = 0;
+      c2->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c2->verts));
+      c2->color = (c->color + (random() % (1 + (bp->ncolors / 3)))
+                   % bp->ncolors);
+    }
+
+  /* Add the verts to the approprate chunks
+   */
+  for (i = 0; i < c->nverts; i++)
+    {
+      qh_vertex_t *v0 = &c->verts[i];
+      int target_chunk = -1;
+      double target_d2 = 9999999;
+      for (j = 0; j < nchunks; j++)
+        {
+          qh_vertex_t *v1 = &c->verts[keys[j]];
+          double X = v1->x - v0->x;
+          double Y = v1->y - v0->y;
+          double Z = v1->z - v0->z;
+          double d2 = X*X + Y*Y + Z*Z;
+          if (d2 < target_d2)
+            {
+              target_d2 = d2;
+              target_chunk = j;
+            }
+        }
+      if (target_chunk == -1) abort();
+
+      c2 = chunks[target_chunk];
+      c2->verts[c2->nverts++] = *v0;
+    }
+
+  for (i = 0; i < nchunks; i++)
+    {
+      c2 = chunks[i];
+      if (i == 0)  /* The one we're gonna keep */
+        pad_chunk (c2, c->nverts);
+      render_chunk (mi, c2);
+    }
+
+  free (keys);
+  return chunks;
+}
+
+
+static void
+tick_crumbler (ModeInfo *mi)
+{
+  crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+  GLfloat ts;
+
+  if (bp->button_down_p) return;
+
+  switch (bp->state) {
+  case IDLE:  ts = 0.02;  break;
+  case SPLIT: ts = 0.01;  break;
+  case PAUSE: ts = 0.008; break;
+  case FLEE:  ts = 0.005; break;
+  case ZOOM:  ts = 0.03;  break;
+  default:    abort();    break;
+  }
+
+  bp->tick += ts * speed;
+
+  if (bp->tick < 1) return;
+
+  bp->tick = 0;
+  bp->state = (bp->state + 1) % (ZOOM + 1);
+
+  switch (bp->state) {
+  case IDLE:
+    {
+      chunk *c = bp->chunks[0];
+      int i;
+
+      /* We already animated it zooming to full size. Now make it real. */
+      GLfloat X = (c->max.x - c->min.x);
+      GLfloat Y = (c->max.y - c->min.y);
+      GLfloat Z = (c->max.z - c->min.z);
+      GLfloat s = 1 / MAX(X, MAX(Y, Z));
+
+      for (i = 0; i < c->nverts; i++)
+        {
+          c->verts[i].x *= s;
+          c->verts[i].y *= s;
+          c->verts[i].z *= s;
+        }
+
+      /* Re-render it to move the verts in the display list too.
+         This also recomputes min, max and mid.
+       */
+      render_chunk (mi, c);
+      break;
+    }
+
+  case SPLIT:
+    {
+      chunk *c = bp->chunks[0];
+      int frac = (fracture >= 2 ? fracture : 2 + (2 * (random() % 5)));
+      chunk **chunks = split_chunk (mi, c, frac);
+      if (bp->nchunks != 1) abort();
+      if (bp->ghost) abort();
+      bp->ghost = c;
+      free (bp->chunks);
+      bp->chunks = chunks;
+      bp->nchunks = frac;
+      break;
+    }
+
+  case PAUSE:
+    break;
+
+  case FLEE:
+    if (bp->ghost) free_chunk (bp->ghost);
+    bp->ghost = 0;
+    break;
+
+  case ZOOM:
+    {
+      chunk *c = bp->chunks[0];
+      int i;
+      for (i = 1; i < bp->nchunks; i++)
+        free_chunk (bp->chunks[i]);
+      bp->nchunks = 1;
+
+      /* We already animated the remaining chunk moving toward the origin.
+         Make it real.
+      */
+      for (i = 0; i < c->nverts; i++)
+        {
+          c->verts[i].x -= c->mid.x;
+          c->verts[i].y -= c->mid.y;
+          c->verts[i].z -= c->mid.z;
+        }
+
+      /* Re-render it to move the verts in the display list too.
+         This also recomputes min, max and mid (now 0).
+       */
+      render_chunk (mi, c);
+      break;
+    }
+
+  default: abort(); break;
+  }
+}
+
+
+static GLfloat
+ease_fn (GLfloat r)
+{
+  return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
+}
+
+
+static GLfloat
+ease_ratio (GLfloat r)
+{
+  GLfloat ease = 0.35;
+  if      (r <= 0)     return 0;
+  else if (r >= 1)     return 1;
+  else if (r <= ease)  return     ease * ease_fn (r / ease);
+  else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
+  else                 return r;
+}
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_crumbler (ModeInfo *mi, int width, int height)
+{
+  GLfloat h = (GLfloat) height / (GLfloat) width;
+  int y = 0;
+
+  if (width > height * 5) {   /* tiny window: show middle */
+    height = width * 9/16;
+    y = -height/2;
+    h = height / (GLfloat) width;
+  }
+
+  glViewport (0, y, (GLint) width, (GLint) height);
+
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective (30.0, 1/h, 1.0, 100.0);
+
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  gluLookAt( 0.0, 0.0, 30.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
+
+# ifdef HAVE_MOBILE    /* Keep it the same relative size when rotated. */
+  {
+    int o = (int) current_device_rotation();
+    if (o != 0 && o != 180 && o != -180)
+      glScalef (1/h, 1/h, 1/h);
+  }
+# endif
+
+  glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+ENTRYPOINT Bool
+crumbler_handle_event (ModeInfo *mi, XEvent *event)
+{
+  crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+
+  if (gltrackball_event_handler (event, bp->trackball,
+                                 MI_WIDTH (mi), MI_HEIGHT (mi),
+                                 &bp->button_down_p))
+    return True;
+
+  return False;
+}
+
+
+ENTRYPOINT void 
+init_crumbler (ModeInfo *mi)
+{
+  crumbler_configuration *bp;
+  int wire = MI_IS_WIREFRAME(mi);
+  int i;
+
+  MI_INIT (mi, bps);
+  bp = &bps[MI_SCREEN(mi)];
+
+  bp->glx_context = init_GL(mi);
+
+  reshape_crumbler (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+  if (!wire)
+    {
+      GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
+      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+      GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+      GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
+
+      glEnable(GL_LIGHTING);
+      glEnable(GL_LIGHT0);
+      glEnable(GL_DEPTH_TEST);
+      glEnable(GL_CULL_FACE);
+
+      glLightfv(GL_LIGHT0, GL_POSITION, pos);
+      glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
+      glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
+      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+
+      glEnable (GL_BLEND);
+      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    }
+
+  {
+    double spin_speed   = 0.5 * speed;
+    double spin_accel   = 0.3;
+    double wander_speed = 0.01 * speed;
+
+    bp->rot = make_rotator (do_spin ? spin_speed : 0,
+                            do_spin ? spin_speed : 0,
+                            do_spin ? spin_speed : 0,
+                            spin_accel,
+                            do_wander ? wander_speed : 0,
+                            True);
+    bp->trackball = gltrackball_init (True);
+  }
+
+  bp->ncolors = 1024;
+  bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
+  make_smooth_colormap (0, 0, 0,
+                        bp->colors, &bp->ncolors,
+                        False, 0, False);
+
+  /* brighter colors, please... */
+  for (i = 0; i < bp->ncolors; i++)
+    {
+# undef R
+# define R(F) F = 65535 * (0.3 + 0.7 * ((F) / 65535.0))
+      R (bp->colors[i].red);
+      R (bp->colors[i].green);
+      R (bp->colors[i].blue);
+# undef R
+    }
+
+  {
+    chunk *c;
+    bp->nchunks = 1;
+    bp->chunks = (chunk **) calloc (bp->nchunks, sizeof(*bp->chunks));
+    c = make_chunk();
+    bp->chunks[0] = c;
+    c->nverts = 4500 * density;
+    c->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c->verts));
+    make_point_cloud (c->verts, c->nverts);
+
+    /* Let's shrink it to a point then zoom in. */
+    bp->state = ZOOM;
+    bp->tick = 0;
+    for (i = 0; i < c->nverts; i++)
+      {
+        c->verts[i].x /= 500;
+        c->verts[i].y /= 500;
+        c->verts[i].z /= 500;
+      }
+
+    render_chunk (mi, c);
+  }
+}
+
+
+static void
+draw_chunk (ModeInfo *mi, chunk *c, GLfloat alpha)
+{
+  crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+  GLfloat color[4];
+
+  color[0] = bp->colors[c->color].red   / 65536.0;
+  color[1] = bp->colors[c->color].green / 65536.0;
+  color[2] = bp->colors[c->color].blue  / 65536.0;
+  color[3] = alpha;
+  glColor4fv (color);
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
+
+  c->color += c->color_shift;
+  while (c->color < 0) c->color += bp->ncolors;
+  while (c->color >= bp->ncolors) c->color -= bp->ncolors;
+
+  glCallList (c->dlist);
+  mi->polygon_count += c->polygon_count;
+}
+
+
+ENTRYPOINT void
+draw_crumbler (ModeInfo *mi)
+{
+  int wire = MI_IS_WIREFRAME(mi);
+  crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+  GLfloat alpha = 1;
+  int i;
+
+  static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
+  static const GLfloat bshiny    = 128.0;
+
+  if (!bp->glx_context)
+    return;
+
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
+
+  tick_crumbler (mi);
+
+  glShadeModel(GL_SMOOTH);
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  glEnable(GL_CULL_FACE);
+
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushMatrix ();
+
+  {
+    double x, y, z;
+    get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
+    glTranslatef((x - 0.5) * 8,
+                 (y - 0.5) * 8,
+                 (z - 0.5) * 15);
+
+    gltrackball_rotate (bp->trackball);
+
+    get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
+    glRotatef (x * 360, 1.0, 0.0, 0.0);
+    glRotatef (y * 360, 0.0, 1.0, 0.0);
+    glRotatef (z * 360, 0.0, 0.0, 1.0);
+  }
+
+  mi->polygon_count = 0;
+
+  glMaterialfv (GL_FRONT, GL_SPECULAR, bspec);
+  glMateriali  (GL_FRONT, GL_SHININESS, bshiny);
+
+  if (do_wander)
+    glScalef (10, 10, 10);
+  else
+    glScalef (13, 13, 13);
+
+  alpha = 1;
+  for (i = 0; i < bp->nchunks; i++)
+    {
+      chunk *c = bp->chunks[i];
+
+      glPushMatrix();
+
+      switch (bp->state) {
+        case FLEE:
+          {
+            GLfloat r = ease_ratio (bp->tick);
+            /* Move everybody toward the origin, so that chunk #0 ends up
+               centered there. */
+            glTranslatef (-r * c->mid.x,
+                          -r * c->mid.y,
+                          -r * c->mid.z);
+            if (i != 0)
+              {
+                /* Move this chunk away from the center, along a vector from
+                   the origin to its midpoint. */
+                GLfloat d2 = r * 6;
+                glTranslatef (c->vec.x * d2, c->vec.y * d2, c->vec.z * d2);
+                alpha = 1 - r;
+              }
+          }
+          break;
+
+      case ZOOM:
+        {
+          chunk *c = bp->chunks[0];
+          GLfloat X = (c->max.x - c->min.x);
+          GLfloat Y = (c->max.y - c->min.y);
+          GLfloat Z = (c->max.z - c->min.z);
+          GLfloat size0 = MAX(X, MAX(Y, Z));
+          GLfloat size1 = 1.0;
+          GLfloat r = 1 - ease_ratio (bp->tick);
+          GLfloat s = 1 / (size0 + r * (size1 - size0));
+          glScalef (s, s, s);
+        }
+        break;
+
+      default:
+        break;
+      }
+
+      draw_chunk (mi, c, alpha);
+      glPopMatrix();
+    }
+
+  /* Draw the old one, fading out. */
+  if (!wire && bp->state == SPLIT && bp->ghost)
+    {
+      GLfloat s;
+      /* alpha = 1 - bp->tick; */
+      alpha = 1;
+      /* s = 0.7 + (0.3 * ease_ratio (1-bp->tick)); */
+      s = 2 * ease_ratio ((1-bp->tick) / 2);
+      s *= 1.01;
+      glScalef (s, s, s);
+      draw_chunk (mi, bp->ghost, alpha);
+    }
+
+  glPopMatrix ();
+
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
+
+  glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE ("Crumbler", crumbler)
+
+#endif /* USE_GL */