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