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