http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / carousel.c
1 /* carousel, Copyright (c) 2005 Jamie Zawinski <jwz@jwz.org>
2  * Loads a sequence of images and rotates them around.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or
10  * implied warranty.
11  *
12  * Created: 21-Feb-2005
13  */
14
15 #include <X11/Intrinsic.h>
16
17 # define PROGCLASS "Carousel"
18 # define HACK_INIT init_carousel
19 # define HACK_DRAW draw_carousel
20 # define HACK_RESHAPE reshape_carousel
21 # define HACK_HANDLE_EVENT carousel_handle_event
22 # define EVENT_MASK        PointerMotionMask
23 # define carousel_opts xlockmore_opts
24
25 # define DEF_SPEED          "1.0"
26 # define DEF_DURATION       "20"
27 # define DEF_TITLES         "True"
28 # define DEF_ZOOM           "True"
29 # define DEF_TILT           "XY"
30 # define DEF_MIPMAP         "True"
31 # define DEF_DEBUG          "False"
32
33 #define DEF_FONT "-*-times-bold-r-normal-*-180-*"
34
35 #define DEFAULTS  "*count:           7                    \n" \
36                   "*delay:           10000                \n" \
37                   "*speed:         " DEF_SPEED           "\n" \
38                   "*duration:      " DEF_DURATION        "\n" \
39                   "*titles:        " DEF_TITLES          "\n" \
40                   "*zoom:          " DEF_ZOOM            "\n" \
41                   "*tilt:          " DEF_TILT            "\n" \
42                   "*debug:         " DEF_DEBUG           "\n" \
43                   "*mipmap:        " DEF_MIPMAP          "\n" \
44                   "*wireframe:       False                \n" \
45                   "*showFPS:         False                \n" \
46                   "*fpsSolid:        True                 \n" \
47                   "*useSHM:          True                 \n" \
48                   "*font:          " DEF_FONT            "\n" \
49                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n"
50
51 # include "xlockmore.h"
52
53 #undef countof
54 #define countof(x) (sizeof((x))/sizeof((*x)))
55
56 #ifdef USE_GL
57
58 #include <GL/glu.h>
59 #include <math.h>
60 #include <sys/time.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include "rotator.h"
64 #include "gltrackball.h"
65 #include "grab-ximage.h"
66 #include "texfont.h"
67
68 extern XtAppContext app;
69
70 typedef struct {
71   double x, y, w, h;
72 } rect;
73
74 typedef enum { NORMAL, OUT, LOADING, IN, DEAD } fade_mode;
75 static int fade_ticks = 60;
76
77 typedef struct {
78   ModeInfo *mi;
79
80   char *title;                  /* the filename of this image */
81   int w, h;                     /* size in pixels of the image */
82   int tw, th;                   /* size in pixels of the texture */
83   XRectangle geom;              /* where in the image the bits are */
84   GLuint texid;                 /* which texture contains the image */
85
86   GLfloat r, theta;             /* radius and rotation on the tube */
87   rotator *rot;                 /* for zoomery */
88   Bool from_top_p;              /* whether this image drops in or rises up */
89   time_t expires;               /* when this image should be replaced */
90   fade_mode mode;               /* in/out animation state */
91   int mode_tick;
92   Bool loaded_p;                /* whether background load is done */
93
94 } image;
95
96
97 typedef struct {
98   GLXContext *glx_context;
99   rotator *rot;
100   trackball_state *trackball;
101   Bool button_down_p;
102   time_t button_down_time;
103
104   int nimages;                  /* how many images are loaded */
105   int images_size;
106   image **images;               /* pointers to the images */
107
108   texture_font_data *texfont;
109
110   fade_mode mode;
111   int mode_tick;
112
113 } carousel_state;
114
115 static carousel_state *sss = NULL;
116
117
118 /* Command-line arguments
119  */
120 static GLfloat speed;       /* animation speed scale factor */
121 static int duration;        /* reload images after this long */
122 static Bool mipmap_p;       /* Use mipmaps instead of single textures. */
123 static Bool titles_p;       /* Display image titles. */
124 static Bool zoom_p;         /* Throb the images in and out as they spin. */
125 static char *tilt_str;
126 static Bool tilt_x_p;       /* Tilt axis towards the viewer */
127 static Bool tilt_y_p;       /* Tilt axis side to side */
128 static Bool debug_p;        /* Be loud and do weird things. */
129
130
131 static XrmOptionDescRec opts[] = {
132   {"-zoom",         ".zoom",          XrmoptionNoArg, "True"  },
133   {"-no-zoom",      ".zoom",          XrmoptionNoArg, "False"  },
134   {"-tilt",         ".tilt",          XrmoptionSepArg, 0  },
135   {"-no-tilt",      ".tilt",          XrmoptionNoArg, ""  },
136   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
137   {"-no-titles",    ".titles",        XrmoptionNoArg, "True"  },
138   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
139   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
140   {"-duration",     ".duration",      XrmoptionSepArg, 0 },
141   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
142   {"-font",         ".font",          XrmoptionSepArg, 0 },
143   {"-speed",        ".speed",         XrmoptionSepArg, 0 },
144 };
145
146 static argtype vars[] = {
147   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,      t_Bool},
148   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,       t_Bool},
149   { &titles_p,      "titles",       "Titles",       DEF_TITLES,      t_Bool},
150   { &zoom_p,        "zoom",         "Zoom",         DEF_ZOOM,        t_Bool},
151   { &tilt_str,      "tilt",         "Tilt",         DEF_TILT,        t_String},
152   { &speed,         "speed",        "Speed",        DEF_SPEED,       t_Float},
153   { &duration,      "duration",     "Duration",     DEF_DURATION,    t_Int},
154 };
155
156 ModeSpecOpt carousel_opts = {countof(opts), opts, countof(vars), vars, NULL};
157
158
159 /* Allocates an image structure and stores it in the list.
160  */
161 static image *
162 alloc_image (ModeInfo *mi)
163 {
164   carousel_state *ss = &sss[MI_SCREEN(mi)];
165   image *img = (image *) calloc (1, sizeof (*img));
166
167   img->mi = mi;
168   img->rot = make_rotator (0, 0, 0, 0, 0.04 * frand(1.0) * speed, False);
169
170   glGenTextures (1, &img->texid);
171   if (img->texid <= 0) abort();
172
173   if (ss->images_size <= ss->nimages)
174     {
175       ss->images_size = (ss->images_size * 1.2) + ss->nimages;
176       ss->images = (image **)
177         realloc (ss->images, ss->images_size * sizeof(*ss->images));
178       if (! ss->images)
179         {
180           fprintf (stderr, "%s: out of memory (%d images)\n",
181                    progname, ss->images_size);
182           exit (1);
183         }
184     }
185
186   ss->images[ss->nimages++] = img;
187
188   return img;
189 }
190
191
192 static void image_loaded_cb (const char *filename, XRectangle *geom,
193                              int image_width, int image_height,
194                              int texture_width, int texture_height,
195                              void *closure);
196
197
198 /* Load a new file into the given image struct.
199  */
200 static void
201 load_image (ModeInfo *mi, image *img, Bool force_sync_p)
202 {
203   carousel_state *ss = &sss[MI_SCREEN(mi)];
204   int wire = MI_IS_WIREFRAME(mi);
205   Bool async_p = !force_sync_p;
206   int i;
207
208   if (debug_p && !wire && img->w != 0)
209     fprintf (stderr, "%s:  dropped %4d x %-4d  %4d x %-4d  \"%s\"\n",
210              progname, img->geom.width, img->geom.height, img->tw, img->th,
211              (img->title ? img->title : "(null)"));
212
213   if (img->title)
214     {
215       free (img->title);
216       img->title = 0;
217     }
218
219   img->mode = LOADING;
220
221   if (wire)
222     image_loaded_cb (0, 0, 0, 0, 0, 0, img);
223   else if (async_p)
224     screen_to_texture_async (mi->xgwa.screen, mi->window,
225                              MI_WIDTH(mi)/2-1,
226                              MI_HEIGHT(mi)/2-1,
227                              mipmap_p, img->texid, image_loaded_cb, img);
228   else
229     {
230       char *filename = 0;
231       XRectangle geom;
232       int iw=0, ih=0, tw=0, th=0;
233       time_t start = time((time_t *) 0);
234       time_t end;
235
236       glBindTexture (GL_TEXTURE_2D, img->texid);
237       if (! screen_to_texture (mi->xgwa.screen, mi->window,
238                                MI_WIDTH(mi)/2-1,
239                                MI_HEIGHT(mi)/2-1,
240                                mipmap_p, &filename, &geom, &iw, &ih, &tw, &th))
241         exit(1);
242       image_loaded_cb (filename, &geom, iw, ih, tw, th, img);
243       if (filename) free (filename);
244
245       /* Push the expire times of all images forward by the amount of time
246          it took to load *this* image, so that we don't count image-loading
247          time against image duration.
248       */
249       end = time((time_t *) 0);
250       for (i = 0; i < ss->nimages; i++)
251         ss->images[i]->expires += end - start;
252     }
253 }
254
255
256 /* Callback that tells us that the texture has been loaded.
257  */
258 static void
259 image_loaded_cb (const char *filename, XRectangle *geom,
260                  int image_width, int image_height,
261                  int texture_width, int texture_height,
262                  void *closure)
263 {
264   image *img = (image *) closure;
265   ModeInfo *mi = img->mi;
266   /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
267   int wire = MI_IS_WIREFRAME(mi);
268
269   if (wire)
270     {
271       img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
272       img->h = MI_HEIGHT (mi);
273       img->geom.width  = img->w;
274       img->geom.height = img->h;
275       goto DONE;
276     }
277
278   if (image_width == 0 || image_height == 0)
279     exit (1);
280
281   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
282   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
283                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
284
285   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
286   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
287
288   img->w  = image_width;
289   img->h  = image_height;
290   img->tw = texture_width;
291   img->th = texture_height;
292   img->geom = *geom;
293   img->title = (filename ? strdup (filename) : 0);
294
295   if (img->title)   /* strip filename to part after last /. */
296     {
297       char *s = strrchr (img->title, '/');
298       if (s) strcpy (img->title, s+1);
299     }
300
301   if (debug_p)
302     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
303              progname,
304              img->geom.width, img->geom.height, img->tw, img->th,
305              (img->title ? img->title : "(null)"));
306
307  DONE:
308
309   /* This image expires N seconds after it finished loading. */
310   img->expires = time((time_t *) 0) + (duration * MI_COUNT(mi));
311
312   img->mode = IN;
313   img->mode_tick = fade_ticks * speed;
314   img->from_top_p = random() & 1;
315   img->loaded_p = True;
316 }
317
318
319
320
321 /* Free the image and texture, after nobody is referencing it.
322  */
323 #if 0
324 static void
325 destroy_image (ModeInfo *mi, image *img)
326 {
327   carousel_state *ss = &sss[MI_SCREEN(mi)];
328   Bool freed_p = False;
329   int i;
330
331   if (!img) abort();
332   if (img->texid <= 0) abort();
333
334   for (i = 0; i < ss->nimages; i++)             /* unlink it from the list */
335     if (ss->images[i] == img)
336       {
337         int j;
338         for (j = i; j < ss->nimages-1; j++)     /* pull remainder forward */
339           ss->images[j] = ss->images[j+1];
340         ss->images[j] = 0;
341         ss->nimages--;
342         freed_p = True;
343         break;
344       }
345
346   if (!freed_p) abort();
347
348   if (debug_p)
349     fprintf (stderr, "%s: unloaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
350              progname,
351              img->geom.width, img->geom.height, img->tw, img->th,
352              (img->title ? img->title : "(null)"));
353
354   if (img->title) free (img->title);
355   glDeleteTextures (1, &img->texid);
356   free (img);
357 }
358 #endif
359
360
361 static void loading_msg (ModeInfo *mi, int n);
362
363 static void
364 load_initial_images (ModeInfo *mi)
365 {
366   carousel_state *ss = &sss[MI_SCREEN(mi)];
367   int i;
368   time_t now;
369   Bool async_p = True;   /* computed after first image */
370
371   for (i = 0; i < MI_COUNT(mi); i++)
372     {
373       image *img;
374       loading_msg (mi, (async_p ? 0 : i));
375       img = alloc_image (mi);
376       img->r = 1.0;
377       img->theta = i * 360.0 / MI_COUNT(mi);
378
379       load_image (mi, img, True);
380       if (i == 0)
381         async_p = !img->loaded_p;
382     }
383
384   /* Wait for the images to load...
385    */
386   while (1)
387     {
388       int j;
389       int count = 0;
390
391       for (j = 0; j < MI_COUNT(mi); j++)
392         if (ss->images[j]->loaded_p)
393           count++;
394       if (count == MI_COUNT(mi))
395         break;
396       loading_msg (mi, count);
397
398       usleep (100000);          /* check every 1/10th sec */
399       if (i++ > 600) abort();   /* if a minute has passed, we're broken */
400
401       while (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
402         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
403     }
404
405   /* Stagger expire times of the first batch of images.
406    */
407   now = time((time_t *) 0);
408   for (i = 0; i < ss->nimages; i++)
409     {
410       image *img = ss->images[i];
411       img->expires = now + (duration * (i + 1));
412       img->mode = NORMAL;
413     }
414
415   /* Instead of always going clockwise, shuffle the expire times
416      of the images so that they drop out in a random order.
417   */
418   for (i = 0; i < ss->nimages; i++)
419     {
420       image *img1 = ss->images[i];
421       image *img2 = ss->images[random() % ss->nimages];
422       time_t swap = img1->expires;
423       img1->expires = img2->expires;
424       img2->expires = swap;
425     }
426 }
427
428
429 void
430 reshape_carousel (ModeInfo *mi, int width, int height)
431 {
432   GLfloat h = (GLfloat) height / (GLfloat) width;
433
434   glViewport (0, 0, (GLint) width, (GLint) height);
435
436   glMatrixMode(GL_PROJECTION);
437   glLoadIdentity();
438   gluPerspective (60.0, 1/h, 1.0, 8.0);
439
440   glMatrixMode(GL_MODELVIEW);
441   glLoadIdentity();
442   gluLookAt( 0.0, 0.0, 2.6,
443              0.0, 0.0, 0.0,
444              0.0, 1.0, 0.0);
445
446   glClear(GL_COLOR_BUFFER_BIT);
447 }
448
449
450 Bool
451 carousel_handle_event (ModeInfo *mi, XEvent *event)
452 {
453   carousel_state *ss = &sss[MI_SCREEN(mi)];
454
455   if (event->xany.type == ButtonPress &&
456       event->xbutton.button == Button1)
457     {
458       if (! ss->button_down_p)
459         ss->button_down_time = time((time_t *) 0);
460       ss->button_down_p = True;
461       gltrackball_start (ss->trackball,
462                          event->xbutton.x, event->xbutton.y,
463                          MI_WIDTH (mi), MI_HEIGHT (mi));
464       return True;
465     }
466   else if (event->xany.type == ButtonRelease &&
467            event->xbutton.button == Button1)
468     {
469       if (ss->button_down_p)
470         {
471           /* Add the time the mouse was held to the expire times of all
472              images, so that mouse-dragging doesn't count against
473              image expiration.
474            */
475           int secs = time((time_t *) 0) - ss->button_down_time;
476           int i;
477           for (i = 0; i < ss->nimages; i++)
478             ss->images[i]->expires += secs;
479         }
480       ss->button_down_p = False;
481       return True;
482     }
483   else if (event->xany.type == ButtonPress &&
484            (event->xbutton.button == Button4 ||
485             event->xbutton.button == Button5))
486     {
487       gltrackball_mousewheel (ss->trackball, event->xbutton.button, 5,
488                               !event->xbutton.state);
489       return True;
490     }
491   else if (event->xany.type == MotionNotify &&
492            ss->button_down_p)
493     {
494       gltrackball_track (ss->trackball,
495                          event->xmotion.x, event->xmotion.y,
496                          MI_WIDTH (mi), MI_HEIGHT (mi));
497       return True;
498     }
499
500   return False;
501 }
502
503
504 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
505  */
506 static void
507 hack_resources (void)
508 {
509 #if 0
510   char *res = "desktopGrabber";
511   char *val = get_string_resource (res, "DesktopGrabber");
512   char buf1[255];
513   char buf2[255];
514   XrmValue value;
515   sprintf (buf1, "%.100s.%.100s", progclass, res);
516   sprintf (buf2, "%.200s -v", val);
517   value.addr = buf2;
518   value.size = strlen(buf2);
519   XrmPutResource (&db, buf1, "String", &value);
520 #endif
521 }
522
523
524 static void
525 loading_msg (ModeInfo *mi, int n)
526 {
527   carousel_state *ss = &sss[MI_SCREEN(mi)];
528   int wire = MI_IS_WIREFRAME(mi);
529   char text[100];
530   static int sw=0, sh=0;
531   GLfloat scale;
532
533   if (wire) return;
534
535   if (n == 0)
536     sprintf (text, "Loading images...");
537   else
538     sprintf (text, "Loading images...  (%d%%)",
539              (int) (n * 100 / MI_COUNT(mi)));
540
541   if (sw == 0)    /* only do this once, so that the string doesn't move. */
542     sw = texture_string_width (ss->texfont, text, &sh);
543
544   scale = sh / (GLfloat) MI_HEIGHT(mi);
545
546   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
547
548   glMatrixMode(GL_PROJECTION);
549   glPushMatrix();
550   glLoadIdentity();
551
552   glMatrixMode(GL_MODELVIEW);
553   glPushMatrix();
554   glLoadIdentity();
555   gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
556
557   glTranslatef ((MI_WIDTH(mi)  - sw) / 2,
558                 (MI_HEIGHT(mi) - sh) / 2,
559                 0);
560   glColor3f (1, 1, 0);
561   glEnable (GL_TEXTURE_2D);
562   print_texture_string (ss->texfont, text);
563   glPopMatrix();
564
565   glMatrixMode(GL_PROJECTION);
566   glPopMatrix();
567
568   glMatrixMode(GL_MODELVIEW);
569
570   glFinish();
571   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
572 }
573
574
575 void
576 init_carousel (ModeInfo *mi)
577 {
578   int screen = MI_SCREEN(mi);
579   carousel_state *ss;
580   int wire = MI_IS_WIREFRAME(mi);
581   
582   if (sss == NULL) {
583     if ((sss = (carousel_state *)
584          calloc (MI_NUM_SCREENS(mi), sizeof(carousel_state))) == NULL)
585       return;
586   }
587   ss = &sss[screen];
588
589   if ((ss->glx_context = init_GL(mi)) != NULL) {
590     reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
591   } else {
592     MI_CLEARWINDOW(mi);
593   }
594
595   if (!tilt_str || !*tilt_str)
596     ;
597   else if (!strcasecmp (tilt_str, "X"))
598     tilt_x_p = 1;
599   else if (!strcasecmp (tilt_str, "Y"))
600     tilt_y_p = 1;
601   else if (!strcasecmp (tilt_str, "XY"))
602     tilt_x_p = tilt_y_p = 1;
603   else
604     {
605       fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n",
606                progname, tilt_str);
607       exit (1);
608     }
609
610   {
611     double spin_speed   = speed * 0.2;    /* rotation of tube around axis */
612     double spin_accel   = speed * 0.1;
613     double wander_speed = speed * 0.001;  /* tilting of axis */
614
615     spin_speed   *= 0.9 + frand(0.2);
616     wander_speed *= 0.9 + frand(0.2);
617
618     ss->rot = make_rotator (spin_speed, spin_speed, spin_speed,
619                             spin_accel, wander_speed, True);
620
621     ss->trackball = gltrackball_init ();
622   }
623
624   glDisable (GL_LIGHTING);
625   glEnable (GL_DEPTH_TEST);
626   glDisable (GL_CULL_FACE);
627
628   if (! wire)
629     {
630       glShadeModel (GL_SMOOTH);
631       glEnable (GL_LINE_SMOOTH);
632       /* This gives us a transparent diagonal slice through each image! */
633       /* glEnable (GL_POLYGON_SMOOTH); */
634       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
635       glEnable (GL_BLEND);
636       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
637       glEnable (GL_ALPHA_TEST);
638
639       glEnable (GL_POLYGON_OFFSET_FILL);
640       glPolygonOffset (1.0, 1.0);
641
642     }
643
644   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
645
646   if (debug_p)
647     hack_resources();
648
649   ss->nimages = 0;
650   ss->images_size = 10;
651   ss->images = (image **) calloc (1, ss->images_size * sizeof(*ss->images));
652
653   ss->mode = IN;
654   ss->mode_tick = fade_ticks * speed;
655
656   load_initial_images (mi);
657 }
658
659
660 void
661 draw_carousel (ModeInfo *mi)
662 {
663   carousel_state *ss = &sss[MI_SCREEN(mi)];
664   int wire = MI_IS_WIREFRAME(mi);
665   static time_t last_time = 0;
666   static time_t now = 0;
667   int i;
668
669   if (!ss->glx_context)
670     return;
671
672   /* Only check the wall clock every 10 frames */
673   {
674     static int tick = 0;
675     if (now == 0 || tick++ > 10)
676       {
677         now = time((time_t *) 0);
678         if (last_time == 0) last_time = now;
679         tick = 0;
680       }
681   }
682
683
684   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
685
686   glPushMatrix();
687
688
689   /* Run the startup "un-shrink" animation.
690    */
691   switch (ss->mode)
692     {
693     case IN:
694       if (--ss->mode_tick <= 0)
695         {
696           ss->mode = NORMAL;
697           last_time = time((time_t *) 0);
698         }
699       break;
700     case NORMAL:
701       break;
702     default:
703       abort();
704     }
705
706
707   /* Scale as per the startup "un-shrink" animation.
708    */
709   if (ss->mode != NORMAL)
710     {
711       GLfloat s = (ss->mode == OUT
712                    ? ss->mode_tick / (fade_ticks * speed)
713                    : (((fade_ticks * speed) - ss->mode_tick + 1) /
714                       (fade_ticks * speed)));
715       glScalef (s, s, s);
716     }
717
718   /* Rotate and tilt as per the user, and the motion modeller.
719    */
720   {
721     double x, y, z;
722     gltrackball_rotate (ss->trackball);
723
724     /* Tilt the tube up or down by up to 30 degrees */
725     get_position (ss->rot, &x, &y, &z, !ss->button_down_p);
726     if (tilt_x_p)
727       glRotatef (15 - (x * 30), 1, 0, 0);
728     if (tilt_y_p)
729       glRotatef (7  - (y * 14), 0, 0, 1);
730
731     /* Only use the Y component of the rotator. */
732     get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p);
733     glRotatef (y * 360, 0, 1, 0);
734   }
735
736   /* Render and update each image on the disc.
737    */
738   for (i = 0; i < ss->nimages; i++)
739     {
740       image *img = ss->images[i];
741       GLfloat texw  = img->geom.width  / (GLfloat) img->tw;
742       GLfloat texh  = img->geom.height / (GLfloat) img->th;
743       GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
744       GLfloat texy1 = img->geom.y / (GLfloat) img->th;
745       GLfloat texx2 = texx1 + texw;
746       GLfloat texy2 = texy1 + texh;
747       GLfloat aspect = img->geom.height / (GLfloat) img->geom.width;
748
749       glBindTexture (GL_TEXTURE_2D, img->texid);
750
751       glPushMatrix();
752
753       /* Position this image on the wheel.
754        */
755       glRotatef (img->theta, 0, 1, 0);
756       glTranslatef (0, 0, img->r);
757
758       /* Scale down the image so that all N images fit on the wheel
759          without bumping in to each other.
760       */
761       {
762         GLfloat t, s;
763         switch (ss->nimages)
764           {
765           case 1:  t = -1.0; s = 1.7; break;
766           case 2:  t = -0.8; s = 1.6; break;
767           case 3:  t = -0.4; s = 1.5; break;
768           case 4:  t = -0.2; s = 1.3; break;
769           default: t =  0.0; s = 6.0 / ss->nimages; break;
770           }
771         glTranslatef (0, 0, t);
772         glScalef (s, s, s);
773       }
774
775       /* Center this image on the wheel plane.
776        */
777       glTranslatef (-0.5, -(aspect/2), 0);
778
779       /* Move as per the "zoom in and out" setting.
780        */
781       if (zoom_p)
782         {
783           double x, y, z;
784           /* Only use the Z component of the rotator for in/out position. */
785           get_position (img->rot, &x, &y, &z, !ss->button_down_p);
786           glTranslatef (0, 0, z/2);
787         }
788
789       /* Compute the "drop in and out" state.
790        */
791       switch (img->mode)
792         {
793         case NORMAL:
794           if (!ss->button_down_p &&
795               now >= img->expires)
796             {
797               img->mode = OUT;
798               img->mode_tick = fade_ticks * speed;
799             }
800           break;
801         case OUT:
802           if (--img->mode_tick <= 0)
803             load_image (mi, img, False);
804           break;
805         case LOADING:
806           /* just wait, with the image off screen. */
807           break;
808         case IN:
809           if (--img->mode_tick <= 0)
810             img->mode = NORMAL;
811           break;
812         default:
813           abort();
814         }
815
816       /* Now drop in/out.
817        */
818       if (img->mode != NORMAL)
819         {
820           GLfloat t = (img->mode == LOADING
821                        ? -100
822                        : img->mode == OUT
823                        ? img->mode_tick / (fade_ticks * speed)
824                        : (((fade_ticks * speed) - img->mode_tick + 1) /
825                           (fade_ticks * speed)));
826           t = 5 * (1 - t);
827           if (img->from_top_p) t = -t;
828           glTranslatef (0, t, 0);
829         }
830
831       /* Draw the image quad.
832        */
833       if (! wire)
834         {
835           glColor3f (1, 1, 1);
836           glNormal3f (0, 0, 1);
837           glEnable (GL_TEXTURE_2D);
838           glBegin (GL_QUADS);
839           glNormal3f (0, 0, 1);
840           glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
841           glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
842           glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
843           glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
844           glEnd();
845         }
846
847       /* Draw a box around it.
848        */
849       glLineWidth (2.0);
850       glColor3f (0.5, 0.5, 0.5);
851       glDisable (GL_TEXTURE_2D);
852       glBegin (GL_LINE_LOOP);
853       glVertex3f (0, 0, 0);
854       glVertex3f (1, 0, 0);
855       glVertex3f (1, aspect, 0);
856       glVertex3f (0, aspect, 0);
857       glEnd();
858
859       /* Draw a title under the image.
860        */
861       if (titles_p)
862         {
863           int sw, sh;
864           GLfloat scale = 0.05;
865           char *title = img->title ? img->title : "(untitled)";
866           sw = texture_string_width (ss->texfont, title, &sh);
867
868           glTranslatef (0, -scale, 0);
869
870           scale /= sh;
871           glScalef (scale, scale, scale);
872
873           glTranslatef (((1/scale) - sw) / 2, 0, 0);
874           glColor3f (1, 1, 1);
875
876           if (!wire)
877             {
878               glEnable (GL_TEXTURE_2D);
879               print_texture_string (ss->texfont, title);
880             }
881           else
882             {
883               glBegin (GL_LINE_LOOP);
884               glVertex3f (0,  0,  0);
885               glVertex3f (sw, 0,  0);
886               glVertex3f (sw, sh, 0);
887               glVertex3f (0,  sh, 0);
888               glEnd();
889             }
890         }
891
892       glPopMatrix();
893     }
894   glPopMatrix();
895
896   if (mi->fps_p) do_fps (mi);
897   glFinish();
898   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
899 }
900
901 #endif /* USE_GL */