summaryrefslogtreecommitdiffstats
path: root/hacks/glx/crumbler.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/crumbler.c')
-rw-r--r--hacks/glx/crumbler.c875
1 files changed, 875 insertions, 0 deletions
diff --git a/hacks/glx/crumbler.c b/hacks/glx/crumbler.c
new file mode 100644
index 0000000..8e783db
--- /dev/null
+++ b/hacks/glx/crumbler.c
@@ -0,0 +1,875 @@
+/* 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 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;
+
+ if (c->nverts <= 3)
+ {
+ fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
+ abort();
+ }
+
+ c->polygon_count = 0;
+ c->min.x = c->min.y = c->min.z = 999999;
+ c->max.x = c->max.y = c->max.z = -999999;
+
+ 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)
+{
+ if (c->dlist)
+ glDeleteLists (c->dlist, 1);
+ free (c->verts);
+ 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;
+ int *keys;
+ int i, j;
+ int retries = 0;
+
+ RETRY:
+ chunks = (chunk **) calloc (nchunks, sizeof(*chunks));
+ keys = (int *) calloc (nchunks, sizeof(*keys));
+
+ for (i = 0; i < nchunks; i++)
+ {
+ /* Fill keys with random numbers that are not duplicates. */
+ Bool ok = True;
+ chunk *c2 = 0;
+ if (nchunks >= c->nverts)
+ {
+ fprintf (stderr, "%s: nverts %d nchunks %d\n", progname,
+ c->nverts, nchunks);
+ abort();
+ }
+ do {
+ keys[i] = random() % c->nverts;
+ ok = True;
+ for (j = 0; j < i; j++)
+ if (keys[i] == keys[j])
+ {
+ ok = False;
+ break;
+ }
+ } 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;
+ chunk *c2 = 0;
+
+ 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;
+ if (c2->nverts > c->nverts) abort();
+ }
+
+ free (keys);
+ keys = 0;
+
+ for (i = 0; i < nchunks; i++)
+ {
+ chunk *c2 = chunks[i];
+
+ /* It is possible that the keys we have chosen have resulted in one or
+ more cells that have 3 or fewer points in them. If that's the case,
+ re-randomize.
+ */
+ if (c2->nverts <= 3)
+ {
+ for (j = 0; j < nchunks; j++)
+ free_chunk (chunks[j]);
+ free (chunks);
+ chunks = 0;
+ if (retries++ > 100)
+ {
+ fprintf(stderr, "%s: unsplittable\n", progname);
+ abort();
+ }
+ goto RETRY;
+ }
+
+ if (i == 0) /* The one we're gonna keep */
+ pad_chunk (c2, c->nverts);
+ render_chunk (mi, c2);
+ }
+
+ 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
+ }
+
+# ifdef HAVE_MOBILE
+# ifdef USE_IPHONE
+ density *= 0.5; /* iPhone 6s runs out of memory at 4500 nverts. */
+# else
+ density *= 0.3; /* Android Nexus_5_8.1 emulator runs out earlier. */
+# endif
+# endif
+
+ {
+ 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);
+}
+
+
+ENTRYPOINT void
+free_crumbler (ModeInfo *mi)
+{
+ crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ free (bp->trackball);
+ free (bp->rot);
+ free (bp->colors);
+ for (i = 0; i < bp->nchunks; i++)
+ free_chunk (bp->chunks[i]);
+ free (bp->chunks);
+}
+
+
+XSCREENSAVER_MODULE ("Crumbler", crumbler)
+
+#endif /* USE_GL */