cf21a94e1cd5d0716001e89ca0fe9aa2a088b138
[xscreensaver] / hacks / glx / crumbler.c
1 /* crumbler, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        30000       \n" \
13                         "*showFPS:      False       \n" \
14                         "*wireframe:    False       \n" \
15                         "*suppressRotationAnimation: True\n" \
16
17 # define free_crumbler 0
18 # define release_crumbler 0
19 #undef countof
20 #define countof(x) (sizeof((x))/sizeof((*x)))
21
22 #include "xlockmore.h"
23 #include "colors.h"
24 #include "rotator.h"
25 #include "quickhull.h"
26 #include "gltrackball.h"
27 #include <ctype.h>
28
29 #ifdef USE_GL /* whole file */
30
31
32 #define DEF_SPIN        "True"
33 #define DEF_WANDER      "True"
34 #define DEF_SPEED       "1.0"
35 #define DEF_DENSITY     "1.0"
36 #define DEF_FRACTURE    "0"
37
38 #undef RANDSIGN
39 #define RANDSIGN() ((random() & 1) ? 1 : -1)
40
41 typedef struct {
42   qh_vertex_t *verts;           /* interior point cloud */
43   int nverts, onverts;
44   qh_vertex_t min, max;         /* enclosing box */
45   qh_vertex_t mid, vec;
46   int polygon_count;
47   GLuint dlist;
48   int color;
49   int color_shift;
50 } chunk;
51
52 typedef struct {
53   GLXContext *glx_context;
54   rotator *rot;
55   trackball_state *trackball;
56   enum { IDLE, SPLIT, PAUSE, FLEE, ZOOM } state;
57   GLfloat tick;
58   Bool button_down_p;
59   int nchunks;
60   chunk **chunks;
61   chunk *ghost;
62
63   int ncolors;
64   XColor *colors;
65 } crumbler_configuration;
66
67 static crumbler_configuration *bps = NULL;
68
69 static Bool do_spin;
70 static GLfloat speed;
71 static GLfloat density;
72 static int fracture;
73 static Bool do_wander;
74
75 static XrmOptionDescRec opts[] = {
76   { "-spin",    ".spin",    XrmoptionNoArg, "True" },
77   { "+spin",    ".spin",    XrmoptionNoArg, "False" },
78   { "-speed",   ".speed",   XrmoptionSepArg, 0 },
79   { "-density", ".density", XrmoptionSepArg, 0 },
80   { "-fracture",".fracture",XrmoptionSepArg, 0 },
81   { "-wander",  ".wander",  XrmoptionNoArg, "True" },
82   { "+wander",  ".wander",  XrmoptionNoArg, "False" }
83 };
84
85 static argtype vars[] = {
86   {&do_spin,   "spin",    "Spin",    DEF_SPIN,    t_Bool},
87   {&do_wander, "wander",  "Wander",  DEF_WANDER,  t_Bool},
88   {&speed,     "speed",   "Speed",   DEF_SPEED,   t_Float},
89   {&density,   "density", "Density", DEF_DENSITY, t_Float},
90   {&fracture,  "fracture","Fracture",DEF_FRACTURE,t_Int},
91 };
92
93 ENTRYPOINT ModeSpecOpt crumbler_opts = {countof(opts), opts, countof(vars), vars, NULL};
94
95
96 /* Create a roughly spherical cloud of N random points.
97  */
98 static void
99 make_point_cloud (qh_vertex_t *verts, int nverts)
100 {
101   int i = 0;
102   while (i < nverts)
103     {
104       verts[i].x = (0.5 - frand(1.0));
105       verts[i].y = (0.5 - frand(1.0));
106       verts[i].z = (0.5 - frand(1.0));
107       if ((verts[i].x * verts[i].x +
108            verts[i].y * verts[i].y +
109            verts[i].z * verts[i].z)
110           < 0.25)
111         i++;
112     }
113 }
114
115
116 static chunk *
117 make_chunk (void)
118 {
119   chunk *c = (chunk *) calloc (1, sizeof(*c));
120   c->dlist = glGenLists (1);
121   c->color_shift = 1 + (random() % 3) * RANDSIGN();
122   return c;
123 }
124
125 static void
126 render_chunk (ModeInfo *mi, chunk *c)
127 {
128   int wire = MI_IS_WIREFRAME(mi);
129   int i, j;
130   qh_mesh_t m;
131   GLfloat d;
132
133   c->polygon_count = 0;
134   c->min.x = c->min.y = c->min.z =  999999;
135   c->max.x = c->max.y = c->max.z = -999999;
136   if (c->nverts <= 3)
137     {
138       fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
139       abort();
140     }
141
142   for (i = 0; i < c->nverts; i++)
143     {
144       if (c->verts[i].x < c->min.x) c->min.x = c->verts[i].x;
145       if (c->verts[i].y < c->min.y) c->min.y = c->verts[i].y;
146       if (c->verts[i].z < c->min.z) c->min.z = c->verts[i].z;
147       if (c->verts[i].x > c->max.x) c->max.x = c->verts[i].x;
148       if (c->verts[i].y > c->max.y) c->max.y = c->verts[i].y;
149       if (c->verts[i].z > c->max.z) c->max.z = c->verts[i].z;
150     }
151
152   c->mid.x = (c->max.x + c->min.x) / 2;
153   c->mid.y = (c->max.y + c->min.y) / 2;
154   c->mid.z = (c->max.z + c->min.z) / 2;
155
156   /* midpoint as normalized vector from origin */
157   d = sqrt (c->mid.x * c->mid.x +
158             c->mid.y * c->mid.y +
159             c->mid.z * c->mid.z);
160   c->vec.x = c->mid.x / d;
161   c->vec.y = c->mid.y / d;
162   c->vec.z = c->mid.z / d;
163
164   if (c->nverts <= 3)
165     {
166       fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
167       abort();
168     }
169
170   m = qh_quickhull3d (c->verts, c->nverts);
171
172   glNewList (c->dlist, GL_COMPILE);
173   if (! wire) glBegin (GL_TRIANGLES);
174   for (i = 0, j = 0; i < m.nindices; i += 3, j++)
175     {
176       qh_vertex_t *v0 = &m.vertices[m.indices[i+0]];
177       qh_vertex_t *v1 = &m.vertices[m.indices[i+1]];
178       qh_vertex_t *v2 = &m.vertices[m.indices[i+2]];
179       qh_vec3_t   *n  = &m.normals[m.normalindices[j]];
180
181       if (i+2 >= m.nindices) abort();
182       if (j >= m.nnormals) abort();
183
184       glNormal3f (n->x, n->y, n->z);
185       if (wire) glBegin(GL_LINE_LOOP);
186       glVertex3f (v0->x, v0->y, v0->z);
187       glVertex3f (v1->x, v1->y, v1->z);
188       glVertex3f (v2->x, v2->y, v2->z);
189       if (wire) glEnd();
190       c->polygon_count++;
191     }
192   if (! wire) glEnd();
193
194   if (wire)
195     {
196       glPointSize (1);
197       glColor3f (0, 1, 0);
198       glBegin (GL_POINTS);
199       for (i = 0; i < c->nverts; i++)
200         {
201           if (i > 0 && i == c->onverts)
202             {
203               glEnd();
204               glColor3f (1, 0, 0);
205               glBegin (GL_POINTS);
206             }
207           glVertex3f (c->verts[i].x, c->verts[i].y, c->verts[i].z);
208         }
209       glEnd();
210     }
211
212   glEndList();
213
214   qh_free_mesh (m);
215 }
216
217
218 static void
219 free_chunk (chunk *c)
220 {
221   free (c->verts);
222   glDeleteLists (c->dlist, 1);
223   free (c);
224 }
225
226
227 /* Make sure the chunk contains at least N points.
228    As we subdivide, the number of points is reduced.
229    This adds new points to the interior that do not
230    affect the shape of the outer hull.
231  */
232 static void
233 pad_chunk (chunk *c, int min)
234 {
235   /* Allocate a new array of size N
236      Copy the old points into it
237      while size < N
238        pick two random points
239        add a point that is somewhere along the line between them
240        (that point will still be inside the old hull)
241    */
242   qh_vertex_t *verts;
243   int i;
244   if (c->nverts >= min) return;
245   if (c->nverts <= 3) abort();
246   verts = (qh_vertex_t *) calloc (min, sizeof(*verts));
247   if (!verts) abort();
248   memcpy (verts, c->verts, c->nverts * sizeof(*verts));
249   i = c->nverts;
250   while (i < min)
251     {
252       qh_vertex_t v;
253       int j0, j1;
254       GLfloat r;
255       j0 = random() % c->nverts;
256       do {
257         j1 = random() % c->nverts;
258       } while (j0 == j1);
259
260       r = 0.2 + frand(0.6);
261 # undef R
262 # define R(F) v.F = c->verts[j0].F + \
263                     r * (fabs (c->verts[j1].F - c->verts[j0].F)) \
264                     * (c->verts[j0].F > c->verts[j1].F ? -1 : 1)
265       R(x);
266       R(y);
267       R(z);
268 # undef R
269
270       /* Sometimes quickhull.c is giving us concave and un-closed polygons.
271          Maybe it gets confused if there are duplicate points?  So reject
272          this point if it is within epsilon of any earlier point.
273        */
274 # if 0 /* Nope, that's not it. */
275       {
276         Bool ok = True;
277         int j;
278         for (j = 0; j < i; j++)
279           {
280             
281             double X = c->verts[j].x - v.x;
282             double Y = c->verts[j].y - v.y;
283             double Z = c->verts[j].z - v.z;
284             double d2 = X*X + Y*Y + Z*Z;
285             if (d2 < 0.0001)
286               {
287                 /* fprintf (stderr, "## REJ %f\n",d2); */
288                 ok = False;
289                 break;
290               }
291           }
292         if (! ok) continue;
293       }
294 # endif
295
296       verts[i++] = v;
297     }
298
299 #if 0
300   fprintf (stdout, " int n = %d;\n", min);
301   fprintf (stdout, " qh_vertex_t v[] = {");
302   for (i = 0; i < min; i++)
303     fprintf(stdout,"{%f,%f,%f},", verts[i].x, verts[i].y, verts[i].z);
304   fprintf (stdout, "};\n\n");
305 #endif
306
307   free (c->verts);
308   c->verts = verts;
309   c->onverts = c->nverts;
310   c->nverts = min;
311
312 #if 0
313   qh_vertex_t *verts2 = (qh_vertex_t *) calloc (n, sizeof(*verts2));
314   memcpy (verts2, v, n * sizeof(*verts2));
315   free (c->verts);
316   c->verts = verts2;
317   c->onverts = 0;
318   c->nverts = n;
319 #endif
320 }
321
322
323 /* Returns a list of N new chunks.
324  */
325 static chunk **
326 split_chunk (ModeInfo *mi, chunk *c, int nchunks)
327 {
328   /* Pick N key-points from the cloud.
329      Create N new chunks.
330      For each old point:
331        It goes in chunk N if it is closest to key-point N.
332      Free old chunk.
333      for each new chunk
334        render_chunk
335    */
336   crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
337   chunk **chunks = (chunk **) calloc (nchunks, sizeof(*chunks));
338   int *keys = (int *) calloc (nchunks, sizeof(*keys));
339   int i, j;
340   chunk *c2;
341
342   for (i = 0; i < nchunks; i++)
343     {
344       /* Fill keys with random numbers that are not duplicates. */
345       Bool ok = True;
346       if (nchunks >= c->nverts)
347         {
348           fprintf (stderr, "%s: nverts %d nchunks %d\n", progname,
349                    c->nverts, nchunks);
350           abort();
351         }
352       do {
353         keys[i] = random() % c->nverts;
354         for (j = 0; j < i; j++)
355           if (keys[i] == keys[j])
356             {
357               ok = False;
358               break;
359             }
360         ok = True;
361       } while (!ok);
362
363       c2 = make_chunk();
364       chunks[i] = c2;
365       chunks[i]->nverts = 0;
366       c2->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c2->verts));
367       c2->color = (c->color + (random() % (1 + (bp->ncolors / 3)))
368                    % bp->ncolors);
369     }
370
371   /* Add the verts to the approprate chunks
372    */
373   for (i = 0; i < c->nverts; i++)
374     {
375       qh_vertex_t *v0 = &c->verts[i];
376       int target_chunk = -1;
377       double target_d2 = 9999999;
378       for (j = 0; j < nchunks; j++)
379         {
380           qh_vertex_t *v1 = &c->verts[keys[j]];
381           double X = v1->x - v0->x;
382           double Y = v1->y - v0->y;
383           double Z = v1->z - v0->z;
384           double d2 = X*X + Y*Y + Z*Z;
385           if (d2 < target_d2)
386             {
387               target_d2 = d2;
388               target_chunk = j;
389             }
390         }
391       if (target_chunk == -1) abort();
392
393       c2 = chunks[target_chunk];
394       c2->verts[c2->nverts++] = *v0;
395     }
396
397   for (i = 0; i < nchunks; i++)
398     {
399       c2 = chunks[i];
400       if (i == 0)  /* The one we're gonna keep */
401         pad_chunk (c2, c->nverts);
402       render_chunk (mi, c2);
403     }
404
405   free (keys);
406   return chunks;
407 }
408
409
410 static void
411 tick_crumbler (ModeInfo *mi)
412 {
413   crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
414   GLfloat ts;
415
416   if (bp->button_down_p) return;
417
418   switch (bp->state) {
419   case IDLE:  ts = 0.02;  break;
420   case SPLIT: ts = 0.01;  break;
421   case PAUSE: ts = 0.008; break;
422   case FLEE:  ts = 0.005; break;
423   case ZOOM:  ts = 0.03;  break;
424   default:    abort();    break;
425   }
426
427   bp->tick += ts * speed;
428
429   if (bp->tick < 1) return;
430
431   bp->tick = 0;
432   bp->state = (bp->state + 1) % (ZOOM + 1);
433
434   switch (bp->state) {
435   case IDLE:
436     {
437       chunk *c = bp->chunks[0];
438       int i;
439
440       /* We already animated it zooming to full size. Now make it real. */
441       GLfloat X = (c->max.x - c->min.x);
442       GLfloat Y = (c->max.y - c->min.y);
443       GLfloat Z = (c->max.z - c->min.z);
444       GLfloat s = 1 / MAX(X, MAX(Y, Z));
445
446       for (i = 0; i < c->nverts; i++)
447         {
448           c->verts[i].x *= s;
449           c->verts[i].y *= s;
450           c->verts[i].z *= s;
451         }
452
453       /* Re-render it to move the verts in the display list too.
454          This also recomputes min, max and mid.
455        */
456       render_chunk (mi, c);
457       break;
458     }
459
460   case SPLIT:
461     {
462       chunk *c = bp->chunks[0];
463       int frac = (fracture >= 2 ? fracture : 2 + (2 * (random() % 5)));
464       chunk **chunks = split_chunk (mi, c, frac);
465       if (bp->nchunks != 1) abort();
466       if (bp->ghost) abort();
467       bp->ghost = c;
468       free (bp->chunks);
469       bp->chunks = chunks;
470       bp->nchunks = frac;
471       break;
472     }
473
474   case PAUSE:
475     break;
476
477   case FLEE:
478     if (bp->ghost) free_chunk (bp->ghost);
479     bp->ghost = 0;
480     break;
481
482   case ZOOM:
483     {
484       chunk *c = bp->chunks[0];
485       int i;
486       for (i = 1; i < bp->nchunks; i++)
487         free_chunk (bp->chunks[i]);
488       bp->nchunks = 1;
489
490       /* We already animated the remaining chunk moving toward the origin.
491          Make it real.
492       */
493       for (i = 0; i < c->nverts; i++)
494         {
495           c->verts[i].x -= c->mid.x;
496           c->verts[i].y -= c->mid.y;
497           c->verts[i].z -= c->mid.z;
498         }
499
500       /* Re-render it to move the verts in the display list too.
501          This also recomputes min, max and mid (now 0).
502        */
503       render_chunk (mi, c);
504       break;
505     }
506
507   default: abort(); break;
508   }
509 }
510
511
512 static GLfloat
513 ease_fn (GLfloat r)
514 {
515   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
516 }
517
518
519 static GLfloat
520 ease_ratio (GLfloat r)
521 {
522   GLfloat ease = 0.35;
523   if      (r <= 0)     return 0;
524   else if (r >= 1)     return 1;
525   else if (r <= ease)  return     ease * ease_fn (r / ease);
526   else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
527   else                 return r;
528 }
529
530
531 /* Window management, etc
532  */
533 ENTRYPOINT void
534 reshape_crumbler (ModeInfo *mi, int width, int height)
535 {
536   GLfloat h = (GLfloat) height / (GLfloat) width;
537   int y = 0;
538
539   if (width > height * 5) {   /* tiny window: show middle */
540     height = width * 9/16;
541     y = -height/2;
542     h = height / (GLfloat) width;
543   }
544
545   glViewport (0, y, (GLint) width, (GLint) height);
546
547   glMatrixMode(GL_PROJECTION);
548   glLoadIdentity();
549   gluPerspective (30.0, 1/h, 1.0, 100.0);
550
551   glMatrixMode(GL_MODELVIEW);
552   glLoadIdentity();
553   gluLookAt( 0.0, 0.0, 30.0,
554              0.0, 0.0, 0.0,
555              0.0, 1.0, 0.0);
556
557 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
558   {
559     int o = (int) current_device_rotation();
560     if (o != 0 && o != 180 && o != -180)
561       glScalef (1/h, 1/h, 1/h);
562   }
563 # endif
564
565   glClear(GL_COLOR_BUFFER_BIT);
566 }
567
568
569 ENTRYPOINT Bool
570 crumbler_handle_event (ModeInfo *mi, XEvent *event)
571 {
572   crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
573
574   if (gltrackball_event_handler (event, bp->trackball,
575                                  MI_WIDTH (mi), MI_HEIGHT (mi),
576                                  &bp->button_down_p))
577     return True;
578
579   return False;
580 }
581
582
583 ENTRYPOINT void 
584 init_crumbler (ModeInfo *mi)
585 {
586   crumbler_configuration *bp;
587   int wire = MI_IS_WIREFRAME(mi);
588   int i;
589
590   MI_INIT (mi, bps);
591   bp = &bps[MI_SCREEN(mi)];
592
593   bp->glx_context = init_GL(mi);
594
595   reshape_crumbler (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
596
597   if (!wire)
598     {
599       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
600       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
601       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
602       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
603
604       glEnable(GL_LIGHTING);
605       glEnable(GL_LIGHT0);
606       glEnable(GL_DEPTH_TEST);
607       glEnable(GL_CULL_FACE);
608
609       glLightfv(GL_LIGHT0, GL_POSITION, pos);
610       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
611       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
612       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
613
614       glEnable (GL_BLEND);
615       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
616     }
617
618   {
619     double spin_speed   = 0.5 * speed;
620     double spin_accel   = 0.3;
621     double wander_speed = 0.01 * speed;
622
623     bp->rot = make_rotator (do_spin ? spin_speed : 0,
624                             do_spin ? spin_speed : 0,
625                             do_spin ? spin_speed : 0,
626                             spin_accel,
627                             do_wander ? wander_speed : 0,
628                             True);
629     bp->trackball = gltrackball_init (True);
630   }
631
632   bp->ncolors = 1024;
633   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
634   make_smooth_colormap (0, 0, 0,
635                         bp->colors, &bp->ncolors,
636                         False, 0, False);
637
638   /* brighter colors, please... */
639   for (i = 0; i < bp->ncolors; i++)
640     {
641 # undef R
642 # define R(F) F = 65535 * (0.3 + 0.7 * ((F) / 65535.0))
643       R (bp->colors[i].red);
644       R (bp->colors[i].green);
645       R (bp->colors[i].blue);
646 # undef R
647     }
648
649   {
650     chunk *c;
651     bp->nchunks = 1;
652     bp->chunks = (chunk **) calloc (bp->nchunks, sizeof(*bp->chunks));
653     c = make_chunk();
654     bp->chunks[0] = c;
655     c->nverts = 4500 * density;
656     c->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c->verts));
657     make_point_cloud (c->verts, c->nverts);
658
659     /* Let's shrink it to a point then zoom in. */
660     bp->state = ZOOM;
661     bp->tick = 0;
662     for (i = 0; i < c->nverts; i++)
663       {
664         c->verts[i].x /= 500;
665         c->verts[i].y /= 500;
666         c->verts[i].z /= 500;
667       }
668
669     render_chunk (mi, c);
670   }
671 }
672
673
674 static void
675 draw_chunk (ModeInfo *mi, chunk *c, GLfloat alpha)
676 {
677   crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
678   GLfloat color[4];
679
680   color[0] = bp->colors[c->color].red   / 65536.0;
681   color[1] = bp->colors[c->color].green / 65536.0;
682   color[2] = bp->colors[c->color].blue  / 65536.0;
683   color[3] = alpha;
684   glColor4fv (color);
685   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
686
687   c->color += c->color_shift;
688   while (c->color < 0) c->color += bp->ncolors;
689   while (c->color >= bp->ncolors) c->color -= bp->ncolors;
690
691   glCallList (c->dlist);
692   mi->polygon_count += c->polygon_count;
693 }
694
695
696 ENTRYPOINT void
697 draw_crumbler (ModeInfo *mi)
698 {
699   int wire = MI_IS_WIREFRAME(mi);
700   crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
701   Display *dpy = MI_DISPLAY(mi);
702   Window window = MI_WINDOW(mi);
703   GLfloat alpha = 1;
704   int i;
705
706   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
707   static const GLfloat bshiny    = 128.0;
708
709   if (!bp->glx_context)
710     return;
711
712   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
713
714   tick_crumbler (mi);
715
716   glShadeModel(GL_SMOOTH);
717   glEnable(GL_DEPTH_TEST);
718   glEnable(GL_NORMALIZE);
719   glEnable(GL_CULL_FACE);
720
721   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
722
723   glPushMatrix ();
724
725   {
726     double x, y, z;
727     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
728     glTranslatef((x - 0.5) * 8,
729                  (y - 0.5) * 8,
730                  (z - 0.5) * 15);
731
732     gltrackball_rotate (bp->trackball);
733
734     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
735     glRotatef (x * 360, 1.0, 0.0, 0.0);
736     glRotatef (y * 360, 0.0, 1.0, 0.0);
737     glRotatef (z * 360, 0.0, 0.0, 1.0);
738   }
739
740   mi->polygon_count = 0;
741
742   glMaterialfv (GL_FRONT, GL_SPECULAR, bspec);
743   glMateriali  (GL_FRONT, GL_SHININESS, bshiny);
744
745   if (do_wander)
746     glScalef (10, 10, 10);
747   else
748     glScalef (13, 13, 13);
749
750   alpha = 1;
751   for (i = 0; i < bp->nchunks; i++)
752     {
753       chunk *c = bp->chunks[i];
754
755       glPushMatrix();
756
757       switch (bp->state) {
758         case FLEE:
759           {
760             GLfloat r = ease_ratio (bp->tick);
761             /* Move everybody toward the origin, so that chunk #0 ends up
762                centered there. */
763             glTranslatef (-r * c->mid.x,
764                           -r * c->mid.y,
765                           -r * c->mid.z);
766             if (i != 0)
767               {
768                 /* Move this chunk away from the center, along a vector from
769                    the origin to its midpoint. */
770                 GLfloat d2 = r * 6;
771                 glTranslatef (c->vec.x * d2, c->vec.y * d2, c->vec.z * d2);
772                 alpha = 1 - r;
773               }
774           }
775           break;
776
777       case ZOOM:
778         {
779           chunk *c = bp->chunks[0];
780           GLfloat X = (c->max.x - c->min.x);
781           GLfloat Y = (c->max.y - c->min.y);
782           GLfloat Z = (c->max.z - c->min.z);
783           GLfloat size0 = MAX(X, MAX(Y, Z));
784           GLfloat size1 = 1.0;
785           GLfloat r = 1 - ease_ratio (bp->tick);
786           GLfloat s = 1 / (size0 + r * (size1 - size0));
787           glScalef (s, s, s);
788         }
789         break;
790
791       default:
792         break;
793       }
794
795       draw_chunk (mi, c, alpha);
796       glPopMatrix();
797     }
798
799   /* Draw the old one, fading out. */
800   if (!wire && bp->state == SPLIT && bp->ghost)
801     {
802       GLfloat s;
803       /* alpha = 1 - bp->tick; */
804       alpha = 1;
805       /* s = 0.7 + (0.3 * ease_ratio (1-bp->tick)); */
806       s = 2 * ease_ratio ((1-bp->tick) / 2);
807       s *= 1.01;
808       glScalef (s, s, s);
809       draw_chunk (mi, bp->ghost, alpha);
810     }
811
812   glPopMatrix ();
813
814   if (mi->fps_p) do_fps (mi);
815   glFinish();
816
817   glXSwapBuffers(dpy, window);
818 }
819
820 XSCREENSAVER_MODULE ("Crumbler", crumbler)
821
822 #endif /* USE_GL */