From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / glx / carousel.c
1 /* carousel, Copyright (c) 2005-2015 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 free_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_JWXYZ
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
236       if (w > h * 5) {   /* tiny window: use 16:9 boxes */
237         h = w * 9/16;
238       }
239
240       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context, w, h,
241                           mipmap_p, frame->loading.texid, 
242                           image_loaded_cb, frame);
243     }
244 }
245
246
247 /* Callback that tells us that the texture has been loaded.
248  */
249 static void
250 image_loaded_cb (const char *filename, XRectangle *geom,
251                  int image_width, int image_height,
252                  int texture_width, int texture_height,
253                  void *closure)
254 {
255   image_frame *frame = (image_frame *) closure;
256   ModeInfo *mi = frame->mi;
257   carousel_state *ss = &sss[MI_SCREEN(mi)];
258   int wire = MI_IS_WIREFRAME(mi);
259
260   if (wire)
261     {
262       frame->loading.w = MI_WIDTH (mi) * (0.5 + frand (1.0));
263       frame->loading.h = MI_HEIGHT (mi);
264       frame->loading.geom.width  = frame->loading.w;
265       frame->loading.geom.height = frame->loading.h;
266       goto DONE;
267     }
268
269   if (image_width == 0 || image_height == 0)
270     exit (1);
271
272   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
273   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
274                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
275
276   if (ss->anisotropic >= 1.0)
277     glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 
278                      ss->anisotropic);
279
280   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
281   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
282
283   frame->loading.w  = image_width;
284   frame->loading.h  = image_height;
285   frame->loading.tw = texture_width;
286   frame->loading.th = texture_height;
287   frame->loading.geom = *geom;
288
289   if (frame->loading.title)
290     free (frame->loading.title);
291   frame->loading.title = (filename ? strdup (filename) : 0);
292
293   /* xscreensaver-getimage returns paths relative to the image directory
294      now, so leave the sub-directory part in.  Unless it's an absolute path.
295   */
296   if (frame->loading.title && frame->loading.title[0] == '/')
297     {    /* strip filename to part after last /. */
298       char *s = strrchr (frame->loading.title, '/');
299       if (s) strcpy (frame->loading.title, s+1);
300     }
301
302   if (debug_p)
303     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
304              progname,
305              frame->loading.geom.width, 
306              frame->loading.geom.height, 
307              frame->loading.tw, frame->loading.th,
308              (frame->loading.title ? frame->loading.title : "(null)"));
309
310  DONE:
311
312   frame->loaded_p = True;
313
314   if (ss->loads_in_progress <= 0) abort();
315   ss->loads_in_progress--;
316
317   /* This image expires N seconds after it finished loading. */
318   frame->expires = time((time_t *) 0) + (duration * MI_COUNT(mi));
319
320   switch (frame->mode) 
321     {
322     case EARLY:         /* part of the initial batch of images */
323       {
324         image swap = frame->current;
325         frame->current = frame->loading;
326         frame->loading = swap;
327       }
328       break;
329     case LOADING:       /* start dropping the old image out */
330       {
331         frame->mode = OUT;
332         frame->mode_tick = fade_ticks / speed;
333         frame->from_top_p = random() & 1;
334       }
335       break;
336     default:
337       abort();
338     }
339 }
340
341
342 static void loading_msg (ModeInfo *mi, int n);
343
344 static Bool
345 load_initial_images (ModeInfo *mi)
346 {
347   carousel_state *ss = &sss[MI_SCREEN(mi)];
348   int i;
349   Bool all_loaded_p = True;
350   for (i = 0; i < ss->nframes; i++)
351     if (! ss->frames[i]->loaded_p)
352       all_loaded_p = False;
353
354   if (all_loaded_p)
355     {
356       if (ss->nframes < MI_COUNT (mi))
357         {
358           /* The frames currently on the list are fully loaded.
359              Start the next one loading.  (We run the image loader
360              asynchronously, but we load them one at a time.)
361            */
362           load_image (mi, alloc_frame (mi));
363         }
364       else
365         {
366           /* The first batch of images are now all loaded!
367              Stagger the expire times so that they don't all drop out at once.
368            */
369           time_t now = time((time_t *) 0);
370           int i;
371
372           for (i = 0; i < ss->nframes; i++)
373             {
374               image_frame *frame = ss->frames[i];
375               frame->r = 1.0;
376               frame->theta = i * 360.0 / ss->nframes;
377               frame->expires = now + (duration * (i + 1));
378               frame->mode = NORMAL;
379             }
380
381           /* Instead of always going clockwise, shuffle the expire times
382              of the frames so that they drop out in a random order.
383           */
384           for (i = 0; i < ss->nframes; i++)
385             {
386               image_frame *frame1 = ss->frames[i];
387               image_frame *frame2 = ss->frames[random() % ss->nframes];
388               time_t swap = frame1->expires;
389               frame1->expires = frame2->expires;
390               frame2->expires = swap;
391             }
392
393           ss->awaiting_first_images_p = False;
394         }
395     }
396       
397   loading_msg (mi, ss->nframes-1);
398
399   return !ss->awaiting_first_images_p;
400 }
401
402
403 ENTRYPOINT void
404 reshape_carousel (ModeInfo *mi, int width, int height)
405 {
406   GLfloat h = (GLfloat) height / (GLfloat) width;
407   int y = 0;
408
409   if (width > height * 5) {   /* tiny window: show middle */
410     height = width * 9/16;
411     y = -height/2;
412     h = height / (GLfloat) width;
413   }
414
415   glViewport (0, y, (GLint) width, (GLint) height);
416
417   glMatrixMode(GL_PROJECTION);
418   glLoadIdentity();
419   gluPerspective (60.0, 1/h, 1.0, 8.0);
420
421   glMatrixMode(GL_MODELVIEW);
422   glLoadIdentity();
423   gluLookAt( 0.0, 0.0, 2.6,
424              0.0, 0.0, 0.0,
425              0.0, 1.0, 0.0);
426
427   glClear(GL_COLOR_BUFFER_BIT);
428 }
429
430
431 ENTRYPOINT Bool
432 carousel_handle_event (ModeInfo *mi, XEvent *event)
433 {
434   carousel_state *ss = &sss[MI_SCREEN(mi)];
435
436   if (event->xany.type == ButtonPress &&
437       event->xbutton.button == Button1)
438     {
439       if (! ss->button_down_p)
440         ss->button_down_time = time((time_t *) 0);
441     }
442   else if (event->xany.type == ButtonRelease &&
443            event->xbutton.button == Button1)
444     {
445       if (ss->button_down_p)
446         {
447           /* Add the time the mouse was held to the expire times of all
448              frames, so that mouse-dragging doesn't count against
449              image expiration.
450            */
451           int secs = time((time_t *) 0) - ss->button_down_time;
452           int i;
453           for (i = 0; i < ss->nframes; i++)
454             ss->frames[i]->expires += secs;
455         }
456     }
457
458   if (gltrackball_event_handler (event, ss->trackball,
459                                  MI_WIDTH (mi), MI_HEIGHT (mi),
460                                  &ss->button_down_p))
461     return True;
462   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
463     {
464       int i = random() % ss->nframes;
465       ss->frames[i]->expires  = 0;
466       return True;
467     }
468
469   return False;
470 }
471
472
473 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
474  */
475 static void
476 hack_resources (Display *dpy)
477 {
478 # ifndef HAVE_JWXYZ
479   char *res = "desktopGrabber";
480   char *val = get_string_resource (dpy, res, "DesktopGrabber");
481   char buf1[255];
482   char buf2[255];
483   XrmValue value;
484   XrmDatabase db = XtDatabase (dpy);
485   sprintf (buf1, "%.100s.%.100s", progname, res);
486   sprintf (buf2, "%.200s -v", val);
487   value.addr = buf2;
488   value.size = strlen(buf2);
489   XrmPutResource (&db, buf1, "String", &value);
490 # endif /* !HAVE_JWXYZ */
491 }
492
493
494 static void
495 loading_msg (ModeInfo *mi, int n)
496 {
497   carousel_state *ss = &sss[MI_SCREEN(mi)];
498   int wire = MI_IS_WIREFRAME(mi);
499   char text[100];
500
501   if (wire) return;
502
503   if (n == 0)
504     sprintf (text, "Loading images...");
505   else
506     sprintf (text, "Loading images...  (%d%%)",
507              (int) (n * 100 / MI_COUNT(mi)));
508
509   if (ss->loading_sw == 0)
510     {
511       /* only do this once, so that the string doesn't move. */
512       XCharStruct e;
513       texture_string_metrics (ss->texfont, text, &e, 0, 0);
514       ss->loading_sw = e.width;
515       ss->loading_sh = e.ascent + e.descent;
516     }
517
518   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
519
520   glMatrixMode(GL_PROJECTION);
521   glPushMatrix();
522   glLoadIdentity();
523
524   glMatrixMode(GL_MODELVIEW);
525   glPushMatrix();
526   glLoadIdentity();
527
528 /*
529   {
530     double rot = current_device_rotation();
531     glRotatef(rot, 0, 0, 1);
532     if ((rot >  45 && rot <  135) ||
533         (rot < -45 && rot > -135))
534       {
535         GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
536         glScalef (s, 1/s, 1);
537       }
538   }
539 */
540
541 # ifdef HAVE_MOBILE
542   if (MI_WIDTH(mi) < MI_HEIGHT(mi))  /* portrait orientation */
543     {
544       GLfloat s = (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi));
545       glScalef (s, s, s);
546       glTranslatef(-s/2, 0, 0);
547     }
548 # endif
549
550   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
551   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
552                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
553                 0);
554   glColor3f (1, 1, 0);
555   glEnable (GL_TEXTURE_2D);
556   glDisable (GL_DEPTH_TEST);
557   print_texture_string (ss->texfont, text);
558   glEnable (GL_DEPTH_TEST);
559   glPopMatrix();
560
561   glMatrixMode(GL_PROJECTION);
562   glPopMatrix();
563
564   glMatrixMode(GL_MODELVIEW);
565
566   glFinish();
567   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
568 }
569
570
571 ENTRYPOINT void
572 init_carousel (ModeInfo *mi)
573 {
574   int screen = MI_SCREEN(mi);
575   carousel_state *ss;
576   int wire = MI_IS_WIREFRAME(mi);
577   
578   MI_INIT (mi, sss);
579   ss = &sss[screen];
580
581   if ((ss->glx_context = init_GL(mi)) != NULL) {
582     reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
583     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
584   } else {
585     MI_CLEARWINDOW(mi);
586   }
587
588   if (!tilt_str || !*tilt_str)
589     ;
590   else if (!strcasecmp (tilt_str, "0"))
591     ;
592   else if (!strcasecmp (tilt_str, "X"))
593     tilt_x_p = 1;
594   else if (!strcasecmp (tilt_str, "Y"))
595     tilt_y_p = 1;
596   else if (!strcasecmp (tilt_str, "XY"))
597     tilt_x_p = tilt_y_p = 1;
598   else
599     {
600       fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n",
601                progname, tilt_str);
602       exit (1);
603     }
604
605   {
606     double spin_speed   = speed * 0.2;    /* rotation of tube around axis */
607     double spin_accel   = speed * 0.1;
608     double wander_speed = speed * 0.001;  /* tilting of axis */
609
610     spin_speed   *= 0.9 + frand(0.2);
611     wander_speed *= 0.9 + frand(0.2);
612
613     ss->rot = make_rotator (spin_speed, spin_speed, spin_speed,
614                             spin_accel, wander_speed, True);
615
616     ss->trackball = gltrackball_init (False);
617   }
618
619   if (strstr ((char *) glGetString(GL_EXTENSIONS),
620               "GL_EXT_texture_filter_anisotropic"))
621     glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &ss->anisotropic);
622   else
623     ss->anisotropic = 0.0;
624
625   glDisable (GL_LIGHTING);
626   glEnable (GL_DEPTH_TEST);
627   glDisable (GL_CULL_FACE);
628
629   if (! wire)
630     {
631       glShadeModel (GL_SMOOTH);
632       glEnable (GL_LINE_SMOOTH);
633       /* This gives us a transparent diagonal slice through each image! */
634       /* glEnable (GL_POLYGON_SMOOTH); */
635       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
636       glEnable (GL_BLEND);
637       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
638       glEnable (GL_ALPHA_TEST);
639
640       glEnable (GL_POLYGON_OFFSET_FILL);
641       glPolygonOffset (1.0, 1.0);
642
643     }
644
645   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
646
647   if (debug_p)
648     hack_resources (MI_DISPLAY (mi));
649
650   ss->nframes = 0;
651   ss->frames_size = 10;
652   ss->frames = (image_frame **)
653     calloc (1, ss->frames_size * sizeof(*ss->frames));
654
655   ss->mode = IN;
656   ss->mode_tick = fade_ticks / speed;
657
658   ss->awaiting_first_images_p = True;
659 }
660
661
662 static void
663 draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p)
664 {
665   carousel_state *ss = &sss[MI_SCREEN(mi)];
666   int wire = MI_IS_WIREFRAME(mi);
667
668   GLfloat texw  = frame->current.geom.width  / (GLfloat) frame->current.tw;
669   GLfloat texh  = frame->current.geom.height / (GLfloat) frame->current.th;
670   GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw;
671   GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th;
672   GLfloat texx2 = texx1 + texw;
673   GLfloat texy2 = texy1 + texh;
674   GLfloat aspect = ((GLfloat) frame->current.geom.height /
675                     (GLfloat) frame->current.geom.width);
676
677   glBindTexture (GL_TEXTURE_2D, frame->current.texid);
678
679   glPushMatrix();
680
681   /* Position this image on the wheel.
682    */
683   glRotatef (frame->theta, 0, 1, 0);
684   glTranslatef (0, 0, frame->r);
685
686   /* Scale down the image so that all N frames fit on the wheel
687      without bumping in to each other.
688   */
689   {
690     GLfloat t, s;
691     switch (ss->nframes)
692       {
693       case 1:  t = -1.0; s = 1.7; break;
694       case 2:  t = -0.8; s = 1.6; break;
695       case 3:  t = -0.4; s = 1.5; break;
696       case 4:  t = -0.2; s = 1.3; break;
697       default: t =  0.0; s = 6.0 / ss->nframes; break;
698       }
699     glTranslatef (0, 0, t);
700     glScalef (s, s, s);
701   }
702
703   /* Center this image on the wheel plane.
704    */
705   glTranslatef (-0.5, -(aspect/2), 0);
706
707   /* Move as per the "zoom in and out" setting.
708    */
709   if (zoom_p)
710     {
711       double x, y, z;
712       /* Only use the Z component of the rotator for in/out position. */
713       get_position (frame->rot, &x, &y, &z, !ss->button_down_p);
714       glTranslatef (0, 0, z/2);
715     }
716
717   /* Compute the "drop in and out" state.
718    */
719   switch (frame->mode)
720     {
721     case EARLY:
722       abort();
723       break;
724     case NORMAL:
725       if (!ss->button_down_p &&
726           now >= frame->expires &&
727           ss->loads_in_progress == 0)  /* only load one at a time */
728         load_image (mi, frame);
729       break;
730     case LOADING:
731       break;
732     case OUT:
733       if (--frame->mode_tick <= 0) {
734         image swap = frame->current;
735         frame->current = frame->loading;
736         frame->loading = swap;
737
738         frame->mode = IN;
739         frame->mode_tick = fade_ticks / speed;
740       }
741       break;
742     case IN:
743       if (--frame->mode_tick <= 0)
744         frame->mode = NORMAL;
745       break;
746     default:
747       abort();
748     }
749
750   /* Now translate for current in/out state.
751    */
752   if (frame->mode == OUT || frame->mode == IN)
753     {
754       GLfloat t = (frame->mode == OUT
755                    ? frame->mode_tick / (fade_ticks / speed)
756                    : (((fade_ticks / speed) - frame->mode_tick + 1) /
757                       (fade_ticks / speed)));
758       t = 5 * (1 - t);
759       if (frame->from_top_p) t = -t;
760       glTranslatef (0, t, 0);
761     }
762
763   if (body_p)                                   /* Draw the image quad. */
764     {
765       if (! wire)
766         {
767           glColor3f (1, 1, 1);
768           glNormal3f (0, 0, 1);
769           glEnable (GL_TEXTURE_2D);
770           glBegin (GL_QUADS);
771           glNormal3f (0, 0, 1);
772           glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
773           glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
774           glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
775           glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
776           glEnd();
777         }
778
779       /* Draw a box around it.
780        */
781       glLineWidth (2.0);
782       glColor3f (0.5, 0.5, 0.5);
783       glDisable (GL_TEXTURE_2D);
784       glBegin (GL_LINE_LOOP);
785       glVertex3f (0, 0, 0);
786       glVertex3f (1, 0, 0);
787       glVertex3f (1, aspect, 0);
788       glVertex3f (0, aspect, 0);
789       glEnd();
790
791     }
792   else                                  /* Draw a title under the image. */
793     {
794       XCharStruct e;
795       int sw, sh;
796       GLfloat scale = 0.05;
797       char *title = frame->current.title ? frame->current.title : "(untitled)";
798       texture_string_metrics (ss->texfont, title, &e, 0, 0);
799       sw = e.width;
800       sh = e.ascent + e.descent;
801
802       glTranslatef (0, -scale, 0);
803
804       scale /= sh;
805       glScalef (scale, scale, scale);
806
807       glTranslatef (((1/scale) - sw) / 2, 0, 0);
808       glColor3f (1, 1, 1);
809
810       if (!wire)
811         {
812           glEnable (GL_TEXTURE_2D);
813           print_texture_string (ss->texfont, title);
814         }
815       else
816         {
817           glBegin (GL_LINE_LOOP);
818           glVertex3f (0,  0,  0);
819           glVertex3f (sw, 0,  0);
820           glVertex3f (sw, sh, 0);
821           glVertex3f (0,  sh, 0);
822           glEnd();
823         }
824     }
825
826   glPopMatrix();
827 }
828
829
830 ENTRYPOINT void
831 draw_carousel (ModeInfo *mi)
832 {
833   carousel_state *ss = &sss[MI_SCREEN(mi)];
834   int i;
835
836   if (!ss->glx_context)
837     return;
838
839   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
840
841   if (ss->awaiting_first_images_p)
842     if (!load_initial_images (mi))
843       return;
844
845   /* Only check the wall clock every 10 frames */
846   {
847     if (ss->now == 0 || ss->draw_tick++ > 10)
848       {
849         ss->now = time((time_t *) 0);
850         if (ss->last_time == 0) ss->last_time = ss->now;
851         ss->draw_tick = 0;
852       }
853   }
854
855
856   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
857
858   glPushMatrix();
859
860   glRotatef(current_device_rotation(), 0, 0, 1);
861
862
863   /* Run the startup "un-shrink" animation.
864    */
865   switch (ss->mode)
866     {
867     case IN:
868       if (--ss->mode_tick <= 0)
869         {
870           ss->mode = NORMAL;
871           ss->last_time = time((time_t *) 0);
872         }
873       break;
874     case NORMAL:
875       break;
876     default:
877       abort();
878     }
879
880
881   /* Scale as per the startup "un-shrink" animation.
882    */
883   if (ss->mode != NORMAL)
884     {
885       GLfloat s = (ss->mode == OUT
886                    ? ss->mode_tick / (fade_ticks / speed)
887                    : (((fade_ticks / speed) - ss->mode_tick + 1) /
888                       (fade_ticks / speed)));
889       glScalef (s, s, s);
890     }
891
892   /* Rotate and tilt as per the user, and the motion modeller.
893    */
894   {
895     double x, y, z;
896     gltrackball_rotate (ss->trackball);
897
898     /* Tilt the tube up or down by up to 30 degrees */
899     get_position (ss->rot, &x, &y, &z, !ss->button_down_p);
900     if (tilt_x_p)
901       glRotatef (15 - (x * 30), 1, 0, 0);
902     if (tilt_y_p)
903       glRotatef (7  - (y * 14), 0, 0, 1);
904
905     /* Only use the Y component of the rotator. */
906     get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p);
907     glRotatef (y * 360, 0, 1, 0);
908   }
909
910   /* First draw each image, then draw the titles.  GL insists that you
911      draw back-to-front in order to make alpha blending work properly,
912      so we need to draw all of the 100% opaque images before drawing
913      any of the not-100%-opaque titles.
914    */
915   for (i = 0; i < ss->nframes; i++)
916     draw_frame (mi, ss->frames[i], ss->now, True);
917   if (titles_p)
918     for (i = 0; i < ss->nframes; i++)
919       draw_frame (mi, ss->frames[i], ss->now, False);
920
921   glPopMatrix();
922
923   if (mi->fps_p) do_fps (mi);
924   glFinish();
925   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
926 }
927
928 XSCREENSAVER_MODULE ("Carousel", carousel)
929
930 #endif /* USE_GL */