From http://www.jwz.org/xscreensaver/xscreensaver-5.18.tar.gz
[xscreensaver] / hacks / glx / polyhedra-gl.c
1 /* polyhedra, Copyright (c) 2004-2011 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  * Renders 160 different 3D solids, and displays some information about each.
12  *  A new solid is chosen every few seconds.
13  *
14  * This file contains the OpenGL side; computation of the polyhedra themselves
15  * is in "polyhedra.c".
16  */
17
18 #define DEFAULTS        "*delay:        30000         \n" \
19                         "*showFPS:      False         \n" \
20                         "*wireframe:    False         \n" \
21                         "*titleFont:  -*-helvetica-medium-r-normal-*-140-*\n" \
22                         "*titleFont2: -*-helvetica-medium-r-normal-*-100-*\n" \
23                         "*titleFont3: -*-helvetica-medium-r-normal-*-80-*\n"  \
24
25
26 # define refresh_polyhedra 0
27 # define release_polyhedra 0
28 #undef countof
29 #define countof(x) (sizeof((x))/sizeof((*x)))
30
31 #include "xlockmore.h"
32
33 #ifdef HAVE_COCOA
34 # include "jwxyz.h"
35 #else
36 # include <X11/Xlib.h>
37 # include <GL/gl.h>
38 # include <GL/glu.h>
39 #endif
40
41 #ifdef HAVE_JWZGLES
42 # include "jwzgles.h"
43 #endif /* HAVE_JWZGLES */
44
45 #define DEF_SPIN        "True"
46 #define DEF_WANDER      "True"
47 #define DEF_SPEED       "1.0"
48 #define DEF_TITLES      "True"
49 #define DEF_DURATION    "12"
50 #define DEF_WHICH       "random"
51
52 #include "glxfonts.h"
53 #include "normals.h"
54 #include "polyhedra.h"
55 #include "colors.h"
56 #include "rotator.h"
57 #include "gltrackball.h"
58 #include "teapot.h"
59
60 #ifndef HAVE_COCOA
61 # define XK_MISCELLANY
62 # include <X11/keysymdef.h>
63 #endif
64
65 #ifndef HAVE_JWZGLES
66 # define HAVE_TESS
67 #endif
68
69
70 #ifdef USE_GL /* whole file */
71
72 typedef struct {
73   GLXContext *glx_context;
74   rotator *rot;
75   trackball_state *trackball;
76   Bool button_down_p;
77
78   int npolyhedra;
79   polyhedron **polyhedra;
80
81   int which;
82   int change_to;
83   GLuint object_list;
84   GLuint title_list;
85
86   int mode;  /* 0 = normal, 1 = out, 2 = in */
87   int mode_tick;
88
89   int ncolors;
90   XColor *colors;
91
92 # ifdef HAVE_GLBITMAP
93   XFontStruct *xfont1, *xfont2, *xfont3;
94   GLuint font1_dlist, font2_dlist, font3_dlist;
95 # else
96   texture_font_data *font1_data, *font2_data, *font3_data;
97 # endif
98
99   time_t last_change_time;
100   int change_tick;
101
102 } polyhedra_configuration;
103
104 static polyhedra_configuration *bps = NULL;
105
106 static Bool do_spin;
107 static GLfloat speed;
108 static Bool do_wander;
109 static Bool do_titles;
110 static int duration;
111 static int do_which;
112 static char *do_which_str;
113
114 static XrmOptionDescRec opts[] = {
115   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
116   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
117   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
118   { "-wander", ".wander", XrmoptionNoArg, "True" },
119   { "+wander", ".wander", XrmoptionNoArg, "False" },
120   { "-titles", ".titles", XrmoptionNoArg, "True" },
121   { "+titles", ".titles", XrmoptionNoArg, "False" },
122   { "-duration",".duration",XrmoptionSepArg, 0 },
123   { "-which",  ".which",   XrmoptionSepArg, 0 },
124 };
125
126 static argtype vars[] = {
127   {&do_spin,   "spin",   "Spin",   DEF_SPIN,    t_Bool},
128   {&do_wander, "wander", "Wander", DEF_WANDER,  t_Bool},
129   {&do_titles, "titles", "Titles", DEF_TITLES,  t_Bool},
130   {&speed,     "speed",  "Speed",  DEF_SPEED,   t_Float},
131   {&duration,"duration","Duration",DEF_DURATION,t_Int},
132   {&do_which_str,"which", "Which", DEF_WHICH,   t_String},
133 };
134
135 ENTRYPOINT ModeSpecOpt polyhedra_opts = {countof(opts), opts, countof(vars), vars, NULL};
136
137
138 \f
139 /* Calculate the normals at each vertex of a face, and use the sum to
140    decide which normal to assign to the entire face.  This also solves
141    problems caused by nonconvex faces, in most (but not all) cases.
142  */
143 static void
144 kludge_normal (int n, const int *indices, const point *points)
145 {
146   XYZ normal = { 0, 0, 0 };
147   XYZ p = { 0, 0, 0 };
148   int i;
149
150   for (i = 0; i < n; ++i) {
151     int i1 = indices[i];
152     int i2 = indices[(i + 1) % n];
153     int i3 = indices[(i + 2) % n];
154     XYZ p1, p2, p3;
155
156     p1.x = points[i1].x; p1.y = points[i1].y; p1.z = points[i1].z;
157     p2.x = points[i2].x; p2.y = points[i2].y; p2.z = points[i2].z;
158     p3.x = points[i3].x; p3.y = points[i3].y; p3.z = points[i3].z;
159
160     p = calc_normal (p1, p2, p3);
161     normal.x += p.x;
162     normal.y += p.y;
163     normal.z += p.z;
164   }
165
166   /*normalize(&normal);*/
167   if (normal.x == 0 && normal.y == 0 && normal.z == 0) {
168     glNormal3f (p.x, p.y, p.z);
169   } else {
170     glNormal3f (normal.x, normal.y, normal.z);
171   }
172 }
173
174
175 static void
176 load_fonts (ModeInfo *mi)
177 {
178   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
179 # ifdef HAVE_GLBITMAP
180   load_font (mi->dpy, "titleFont",  &bp->xfont1, &bp->font1_dlist);
181   load_font (mi->dpy, "titleFont2", &bp->xfont2, &bp->font2_dlist);
182   load_font (mi->dpy, "titleFont3", &bp->xfont3, &bp->font3_dlist);
183 # else /* !HAVE_GLBITMAP */
184   bp->font1_data = load_texture_font (mi->dpy, "titleFont");
185   bp->font2_data = load_texture_font (mi->dpy, "titleFont2");
186   bp->font3_data = load_texture_font (mi->dpy, "titleFont3");
187 # endif /* !HAVE_GLBITMAP */
188 }
189
190
191
192 static void
193 startup_blurb (ModeInfo *mi)
194 {
195   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
196   const char *s = "Computing polyhedra...";
197 # ifdef HAVE_GLBITMAP
198   XFontStruct *f = bp->xfont1;
199 # else /* !HAVE_GLBITMAP */
200   texture_font_data *f = bp->font1_data;
201 # endif /* !HAVE_GLBITMAP */
202
203   glColor3f (0.8, 0.8, 0);
204   print_gl_string (mi->dpy, 
205 # ifdef HAVE_GLBITMAP
206                    bp->xfont1, bp->font1_dlist,
207 # else /* !HAVE_GLBITMAP */
208                    bp->font1_data,
209 # endif /* !HAVE_GLBITMAP */
210                    mi->xgwa.width, mi->xgwa.height,
211                    mi->xgwa.width - (
212 # ifdef HAVE_GLBITMAP
213                                      string_width (f, s, 0)
214 # else /* !HAVE_GLBITMAP */
215                                      texture_string_width (f, s, 0)
216 # endif /* !HAVE_GLBITMAP */
217                                      + 40),
218                    mi->xgwa.height - 10,
219                    s, False);
220   glFinish();
221   glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
222 }
223
224
225
226 /* Window management, etc
227  */
228 static void new_label (ModeInfo *mi);
229
230 ENTRYPOINT void
231 reshape_polyhedra (ModeInfo *mi, int width, int height)
232 {
233   GLfloat h = (GLfloat) height / (GLfloat) width;
234
235   glViewport (0, 0, (GLint) width, (GLint) height);
236
237   glMatrixMode(GL_PROJECTION);
238   glLoadIdentity();
239   gluPerspective (30.0, 1/h, 1.0, 100.0);
240
241   glMatrixMode(GL_MODELVIEW);
242   glLoadIdentity();
243   gluLookAt( 0.0, 0.0, 30.0,
244              0.0, 0.0, 0.0,
245              0.0, 1.0, 0.0);
246
247   glClear(GL_COLOR_BUFFER_BIT);
248
249   /* need to re-render the text when the window size changes */
250   new_label (mi);
251 }
252
253
254 ENTRYPOINT Bool
255 polyhedra_handle_event (ModeInfo *mi, XEvent *event)
256 {
257   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
258
259   if (event->xany.type == ButtonPress &&
260       event->xbutton.button == Button1)
261     {
262       bp->button_down_p = True;
263       gltrackball_start (bp->trackball,
264                          event->xbutton.x, event->xbutton.y,
265                          MI_WIDTH (mi), MI_HEIGHT (mi));
266       return True;
267     }
268   else if (event->xany.type == ButtonRelease &&
269            event->xbutton.button == Button1)
270     {
271       bp->button_down_p = False;
272       return True;
273     }
274   else if (event->xany.type == ButtonPress &&
275            (event->xbutton.button == Button4 ||
276             event->xbutton.button == Button5 ||
277             event->xbutton.button == Button6 ||
278             event->xbutton.button == Button7))
279     {
280       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
281                               !!event->xbutton.state);
282       return True;
283     }
284   else if (event->xany.type == KeyPress)
285     {
286       KeySym keysym;
287       char c = 0;
288       XLookupString (&event->xkey, &c, 1, &keysym, 0);
289
290       bp->change_to = -1;
291       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
292         bp->change_to = random() % bp->npolyhedra;
293       else if (c == '>' || c == '.' || c == '+' || c == '=' ||
294                keysym == XK_Right || keysym == XK_Up)
295         bp->change_to = (bp->which + 1) % bp->npolyhedra;
296       else if (c == '<' || c == ',' || c == '-' || c == '_' ||
297                c == '\010' || c == '\177' ||
298                keysym == XK_Left || keysym == XK_Down)
299         bp->change_to = (bp->which + bp->npolyhedra - 1) % bp->npolyhedra;
300
301       if (bp->change_to != -1)
302         return True;
303     }
304   else if (event->xany.type == MotionNotify &&
305            bp->button_down_p)
306     {
307       gltrackball_track (bp->trackball,
308                          event->xmotion.x, event->xmotion.y,
309                          MI_WIDTH (mi), MI_HEIGHT (mi));
310       return True;
311     }
312
313   return False;
314 }
315
316
317 static void
318 new_label (ModeInfo *mi)
319 {
320   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
321   polyhedron *p = bp->which >= 0 ? bp->polyhedra[bp->which] : 0;
322
323   glNewList (bp->title_list, GL_COMPILE);
324   if (p && do_titles)
325     {
326       char label[1024];
327       char name2[255];
328       strcpy (name2, p->name);
329       if (*p->class)
330         sprintf (name2 + strlen(name2), "  (%s)", p->class);
331
332       sprintf (label,
333                "Polyhedron %d:   \t%s\n\n"
334                "Wythoff Symbol:\t%s\n"
335                "Vertex Configuration:\t%s\n"
336                "Symmetry Group:\t%s\n"
337             /* "Dual of:              \t%s\n" */
338                "\n"
339                "Faces:\t  %d\n"
340                "Edges:\t  %d\n"
341                "Vertices:\t  %d\n"
342                "Density:\t  %d\n"
343                "Euler:\t%s%d\n",
344                bp->which, name2, p->wythoff, p->config, p->group,
345             /* p->dual, */
346                p->logical_faces, p->nedges, p->logical_vertices,
347                p->density, (p->chi < 0 ? "" : "  "), p->chi);
348
349       {
350 # ifdef HAVE_GLBITMAP
351         XFontStruct *f;
352         GLuint fl;
353 # else /* !HAVE_GLBITMAP */
354         texture_font_data *f;
355 # endif /* !HAVE_GLBITMAP */
356         if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
357 # ifdef HAVE_GLBITMAP
358           f = bp->xfont1, fl = bp->font1_dlist;                /* big font */
359 # else /* !HAVE_GLBITMAP */
360           f = bp->font1_data;
361 # endif /* !HAVE_GLBITMAP */
362         else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
363 # ifdef HAVE_GLBITMAP
364           f = bp->xfont2, fl = bp->font2_dlist;                /* small font */
365 # else /* !HAVE_GLBITMAP */
366           f = bp->font2_data;                                  /* small font */
367 # endif /* !HAVE_GLBITMAP */
368         else
369 # ifdef HAVE_GLBITMAP
370           f = bp->xfont3, fl = bp->font3_dlist;                /* tiny font */
371 # else /* !HAVE_GLBITMAP */
372           f = bp->font3_data;                                  /* tiny font */
373 # endif /* !HAVE_GLBITMAP */
374
375         glColor3f (0.8, 0.8, 0);
376         print_gl_string (mi->dpy, f,
377 # ifdef HAVE_GLBITMAP
378                          fl,
379 # endif /* HAVE_GLBITMAP */
380                          mi->xgwa.width, mi->xgwa.height,
381                          10, mi->xgwa.height - 10,
382                          label, False);
383       }
384     }
385   glEndList ();
386 }
387
388
389 #ifdef HAVE_TESS
390 static void
391 tess_error (GLenum errorCode)
392 {
393   fprintf (stderr, "%s: tesselation error: %s\n",
394            progname, gluErrorString(errorCode));
395   abort();
396 }
397 #endif /* HAVE_TESS */
398
399
400 static void
401 new_polyhedron (ModeInfo *mi)
402 {
403   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
404   polyhedron *p;
405   int wire = MI_IS_WIREFRAME(mi);
406   int i;
407
408   /* Use the GLU polygon tesselator so that nonconvex faces are displayed
409      correctly (e.g., for the "pentagrammic concave deltohedron").
410    */
411 # ifdef HAVE_TESS
412   GLUtesselator *tobj = gluNewTess();
413   gluTessCallback (tobj, GLU_TESS_BEGIN,  (void (*) (void)) &glBegin);
414   gluTessCallback (tobj, GLU_TESS_END,    (void (*) (void)) &glEnd);
415   gluTessCallback (tobj, GLU_TESS_VERTEX, (void (*) (void)) &glVertex3dv);
416   gluTessCallback (tobj, GLU_TESS_ERROR,  (void (*) (void)) &tess_error);
417 # endif /* HAVE_TESS */
418
419   mi->polygon_count = 0;
420
421   bp->ncolors = 128;
422   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
423   make_random_colormap (0, 0, 0,
424                         bp->colors, &bp->ncolors,
425                         True, False, 0, False);
426
427   if (do_which >= bp->npolyhedra)
428     do_which = -1;
429
430   bp->which = (bp->change_to != -1 ? bp->change_to :
431                do_which >= 0 ? do_which :
432                (random() % bp->npolyhedra));
433   bp->change_to = -1;
434   p = bp->polyhedra[bp->which];
435
436   new_label (mi);
437
438   if (wire)
439     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
440
441   glNewList (bp->object_list, GL_COMPILE);
442   if (bp->which == bp->npolyhedra-1)
443     {
444       GLfloat bcolor[4];
445       bcolor[0] = bp->colors[0].red   / 65536.0;
446       bcolor[1] = bp->colors[0].green / 65536.0;
447       bcolor[2] = bp->colors[0].blue  / 65536.0;
448       bcolor[3] = 1.0;
449       if (wire)
450         glColor3f (0, 1, 0);
451       else
452         glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
453
454       glScalef (0.8, 0.8, 0.8);
455       p->nfaces = unit_teapot (6, wire);
456       p->nedges = p->nfaces * 3 / 2;
457       p->npoints = p->nfaces * 3;
458       p->logical_faces = p->nfaces;
459       p->logical_vertices = p->npoints;
460     }
461   else
462     {
463       glFrontFace (GL_CCW);
464       for (i = 0; i < p->nfaces; i++)
465         {
466           int j;
467           face *f = &p->faces[i];
468
469           if (f->color > 64 || f->color < 0) abort();
470           if (wire)
471             glColor3f (0, 1, 0);
472           else
473             {
474               GLfloat bcolor[4];
475               bcolor[0] = bp->colors[f->color].red   / 65536.0;
476               bcolor[1] = bp->colors[f->color].green / 65536.0;
477               bcolor[2] = bp->colors[f->color].blue  / 65536.0;
478               bcolor[3] = 1.0;
479               glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bcolor);
480             }
481
482           kludge_normal (f->npoints, f->points, p->points);
483       
484 # ifdef HAVE_TESS
485           gluTessBeginPolygon (tobj, 0);
486           gluTessBeginContour (tobj);
487           for (j = 0; j < f->npoints; j++)
488             {
489               point *pp = &p->points[f->points[j]];
490               gluTessVertex (tobj, &pp->x, &pp->x);
491             }
492           gluTessEndContour (tobj);
493           gluTessEndPolygon (tobj);
494 # else  /* !HAVE_TESS */
495           glBegin (wire ? GL_LINE_LOOP :
496                    f->npoints == 3 ? GL_TRIANGLES :
497                    f->npoints == 4 ? GL_QUADS :
498                    GL_POLYGON);
499           for (j = 0; j < f->npoints; j++)
500             {
501               point *pp = &p->points[f->points[j]];
502               glVertex3f (pp->x, pp->y, pp->z);
503             }
504           glEnd();
505 # endif /* !HAVE_TESS */
506         }
507     }
508   glEndList ();
509
510   mi->polygon_count += p->nfaces;
511 # ifdef HAVE_TESS
512   gluDeleteTess (tobj);
513 # endif
514 }
515
516
517 static void
518 construct_teapot (ModeInfo *mi)
519 {
520   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
521   int n = bp->npolyhedra-1;
522   polyhedron *p = (polyhedron *) calloc (1, sizeof(*p));
523   p->number = n;
524   p->wythoff = strdup("X00398|1984");
525   p->name = strdup("Teapot");
526   p->dual = strdup("");
527   p->config = strdup("Melitta");
528   p->group = strdup("Teapotahedral (Newell[1975])");
529   p->class = strdup("Utah Teapotahedron");
530   bp->polyhedra[n] = p;
531 }
532
533
534 ENTRYPOINT void 
535 init_polyhedra (ModeInfo *mi)
536 {
537   polyhedra_configuration *bp;
538   int wire = MI_IS_WIREFRAME(mi);
539
540   if (!bps) {
541     bps = (polyhedra_configuration *)
542       calloc (MI_NUM_SCREENS(mi), sizeof (polyhedra_configuration));
543     if (!bps) {
544       fprintf(stderr, "%s: out of memory\n", progname);
545       exit(1);
546     }
547   }
548
549   bp = &bps[MI_SCREEN(mi)];
550
551   bp->glx_context = init_GL(mi);
552
553   bp->which = -1;
554   load_fonts (mi);
555   startup_blurb (mi);
556
557   if (!wire)
558     {
559       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
560       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
561       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
562       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
563
564       glEnable(GL_LIGHTING);
565       glEnable(GL_LIGHT0);
566       glEnable(GL_DEPTH_TEST);
567       /* glEnable(GL_CULL_FACE); */
568
569       /* We need two-sided lighting for polyhedra where both sides of
570          a face are simultaneously visible (e.g., the "X-hemi-Y-hedrons".)
571        */
572       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
573
574       glLightfv(GL_LIGHT0, GL_POSITION, pos);
575       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
576       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
577       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
578     }
579
580   {
581     double spin_speed   = 2.0;
582     double wander_speed = 0.05;
583     double spin_accel   = 0.2;
584
585     bp->rot = make_rotator (do_spin ? spin_speed : 0,
586                             do_spin ? spin_speed : 0,
587                             do_spin ? spin_speed : 0,
588                             spin_accel,
589                             do_wander ? wander_speed : 0,
590                             True);
591     bp->trackball = gltrackball_init ();
592   }
593
594   bp->npolyhedra = construct_polyhedra (&bp->polyhedra);
595   construct_teapot (mi);
596
597   bp->object_list = glGenLists (1);
598   bp->title_list  = glGenLists (1);
599   bp->change_to = -1;
600
601   {
602     int x;
603     char c;
604     do_which = -1;
605     if (!strcasecmp (do_which_str, "random"))
606       ;
607     else if (1 == sscanf (do_which_str, " %d %c", &x, &c))
608       {
609         if (x >= 0 && x < bp->npolyhedra) 
610           do_which = x;
611         else
612           fprintf (stderr, 
613                    "%s: polyhedron %d does not exist: there are only %d.\n",
614                    progname, x, bp->npolyhedra-1);
615       }
616     else if (*do_which_str)
617       {
618         char *s;
619         for (s = do_which_str; *s; s++)
620           if (*s == '-' || *s == '_') *s = ' ';
621
622         for (x = 0; x < bp->npolyhedra; x++)
623           if (!strcasecmp (do_which_str, bp->polyhedra[x]->name) ||
624               !strcasecmp (do_which_str, bp->polyhedra[x]->class) ||
625               !strcasecmp (do_which_str, bp->polyhedra[x]->wythoff) ||
626               !strcasecmp (do_which_str, bp->polyhedra[x]->config))
627             {
628               do_which = x;
629               break;
630             }
631         if (do_which < 0)
632           {
633             fprintf (stderr, "%s: no such polyhedron: \"%s\"\n",
634                      progname, do_which_str);
635             exit (1);
636           }
637       }
638   }
639
640   new_polyhedron (mi);
641   reshape_polyhedra (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
642   clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
643
644 }
645
646
647 ENTRYPOINT void
648 draw_polyhedra (ModeInfo *mi)
649 {
650   polyhedra_configuration *bp = &bps[MI_SCREEN(mi)];
651   Display *dpy = MI_DISPLAY(mi);
652   Window window = MI_WINDOW(mi);
653
654   static const GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
655   GLfloat bshiny    = 128.0;
656
657   if (!bp->glx_context)
658     return;
659
660   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
661
662   if (bp->mode == 0 && do_which >= 0 && bp->change_to < 0)
663     ;
664   else if (bp->mode == 0)
665     {
666       if (bp->change_to >= 0)
667         bp->change_tick = 999, bp->last_change_time = 1;
668       if (bp->change_tick++ > 10)
669         {
670           time_t now = time((time_t *) 0);
671           if (bp->last_change_time == 0) bp->last_change_time = now;
672           bp->change_tick = 0;
673           if (!bp->button_down_p && now - bp->last_change_time >= duration)
674             {
675               bp->mode = 1;    /* go out */
676               bp->mode_tick = 20 * speed;
677               bp->last_change_time = now;
678             }
679         }
680     }
681   else if (bp->mode == 1)   /* out */
682     {
683       if (--bp->mode_tick <= 0)
684         {
685           new_polyhedron (mi);
686           bp->mode_tick = 20 * speed;
687           bp->mode = 2;  /* go in */
688         }
689     }
690   else if (bp->mode == 2)   /* in */
691     {
692       if (--bp->mode_tick <= 0)
693         bp->mode = 0;  /* normal */
694     }
695   else
696     abort();
697
698   glShadeModel(GL_FLAT);
699   glEnable(GL_NORMALIZE);
700
701   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
702
703   glPushMatrix ();
704
705   glScalef(1.1, 1.1, 1.1);
706
707   {
708     double x, y, z;
709     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
710     glTranslatef((x - 0.5) * 8,
711                  (y - 0.5) * 8,
712                  (z - 0.5) * 15);
713
714     /* Do it twice because we don't track the device's orientation. */
715     glRotatef( current_device_rotation(), 0, 0, 1);
716     gltrackball_rotate (bp->trackball);
717     glRotatef(-current_device_rotation(), 0, 0, 1);
718
719     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
720     glRotatef (x * 360, 1.0, 0.0, 0.0);
721     glRotatef (y * 360, 0.0, 1.0, 0.0);
722     glRotatef (z * 360, 0.0, 0.0, 1.0);
723   }
724
725   glScalef (2.0, 2.0, 2.0);
726
727   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  bspec);
728   glMateriali  (GL_FRONT_AND_BACK, GL_SHININESS, bshiny);
729
730   if (bp->mode != 0)
731     {
732       GLfloat s = (bp->mode == 1
733                    ? bp->mode_tick / (20 * speed)
734                    : ((20 * speed) - bp->mode_tick + 1) / (20 * speed));
735       glScalef (s, s, s);
736     }
737
738   glScalef (2, 2, 2);
739   glCallList (bp->object_list);
740   if (bp->mode == 0 && !bp->button_down_p)
741     glCallList (bp->title_list);
742
743   glPopMatrix ();
744
745   if (mi->fps_p) do_fps (mi);
746   glFinish();
747
748   glXSwapBuffers(dpy, window);
749 }
750
751 XSCREENSAVER_MODULE ("Polyhedra", polyhedra)
752
753 #endif /* USE_GL */