http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / glx / photopile.c
1 /* photopile, Copyright (c) 2008-2011 Jens Kilian <jjk@acm.org>
2  * Based on carousel, Copyright (c) 2005-2008 Jamie Zawinski <jwz@jwz.org>
3  * Loads a sequence of images and shuffles them into a pile.
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or
11  * implied warranty.
12  */
13
14 #define DEF_FONT "-*-helvetica-bold-r-normal-*-240-*"
15 #define DEFAULTS  "*count:           7         \n" \
16                   "*delay:           10000     \n" \
17                   "*wireframe:       False     \n" \
18                   "*showFPS:         False     \n" \
19                   "*fpsSolid:        True      \n" \
20                   "*useSHM:          True      \n" \
21                   "*font:          " DEF_FONT "\n" \
22                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
23                   "*grabDesktopImages:   False \n" \
24                   "*chooseRandomImages:  True  \n"
25
26 # define refresh_photopile 0
27 # define release_photopile 0
28 # define photopile_handle_event 0
29
30 #undef countof
31 #define countof(x) (sizeof((x))/sizeof((*x)))
32
33 #ifndef HAVE_COCOA
34 # include <X11/Intrinsic.h>     /* for XrmDatabase in -debug mode */
35 #endif
36 #include <math.h>
37
38 #include "xlockmore.h"
39 #include "grab-ximage.h"
40 #include "texfont.h"
41 #include "dropshadow.h"
42
43 #ifdef USE_GL
44
45 # define DEF_SCALE          "0.4"
46 # define DEF_MAX_TILT       "50"
47 # define DEF_SPEED          "1.0"
48 # define DEF_DURATION       "5"
49 # define DEF_MIPMAP         "True"
50 # define DEF_TITLES         "False"
51 # define DEF_POLAROID       "True"
52 # define DEF_CLIP           "True"
53 # define DEF_SHADOWS        "True"
54 # define DEF_DEBUG          "False"
55
56 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
57
58 typedef struct {
59   GLfloat x, y;                 /* position on screen */
60   GLfloat angle;                /* rotation angle */
61
62 } position;
63
64 typedef struct {
65   Bool loaded_p;                /* true if image can be drawn */
66
67   char *title;                  /* the filename of this image */
68   int w, h;                     /* size in pixels of the image */
69   int tw, th;                   /* size in pixels of the texture */
70   XRectangle geom;              /* where in the image the bits are */
71
72   position pos[4];              /* control points for calculating position */
73
74   GLuint texid;                 /* GL texture ID */
75
76 } image;
77
78
79 typedef enum { EARLY, IN, NORMAL, LOADING, SHUFFLE } fade_mode;
80 static int fade_ticks = 60;
81
82 typedef struct {
83   ModeInfo *mi;
84   GLXContext *glx_context;
85
86   image *frames;                /* pointer to array of images */
87   int nframe;                   /* image being (resp. next to be) loaded */
88
89   GLuint shadow;
90   texture_font_data *texfont;
91   int loading_sw, loading_sh;
92
93   time_t last_time, now;
94   int draw_tick;
95   fade_mode mode;
96   int mode_tick;
97
98 } photopile_state;
99
100 static photopile_state *sss = NULL;
101
102
103 /* Command-line arguments
104  */
105 static GLfloat scale;        /* Scale factor for loading images. */
106 static GLfloat max_tilt;     /* Maximum angle from vertical. */
107 static GLfloat speed;        /* Animation speed scale factor. */
108 static int duration;         /* Reload images after this long. */
109 static Bool mipmap_p;        /* Use mipmaps instead of single textures. */
110 static Bool titles_p;        /* Display image titles. */
111 static Bool polaroid_p;      /* Use instant-film look for images. */
112 static Bool clip_p;          /* Clip images instead of scaling for -polaroid. */static Bool shadows_p;       /* Draw drop shadows. */
113 static Bool debug_p;         /* Be loud and do weird things. */
114
115
116 static XrmOptionDescRec opts[] = {
117   {"-scale",        ".scale",         XrmoptionSepArg, 0 },
118   {"-maxTilt",      ".maxTilt",       XrmoptionSepArg, 0 },
119   {"-speed",        ".speed",         XrmoptionSepArg, 0 },
120   {"-duration",     ".duration",      XrmoptionSepArg, 0 },
121   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
122   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
123   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
124   {"-no-titles",    ".titles",        XrmoptionNoArg, "False" },
125   {"-polaroid",     ".polaroid",      XrmoptionNoArg, "True"  },
126   {"-no-polaroid",  ".polaroid",      XrmoptionNoArg, "False" },
127   {"-clip",         ".clip",          XrmoptionNoArg, "True"  },
128   {"-no-clip",      ".clip",          XrmoptionNoArg, "False" },
129   {"-shadows",      ".shadows",       XrmoptionNoArg, "True"  },
130   {"-no-shadows",   ".shadows",       XrmoptionNoArg, "False" },
131   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
132   {"-font",         ".font",          XrmoptionSepArg, 0 },
133 };
134
135 static argtype vars[] = {
136   { &scale,         "scale",        "Scale",        DEF_SCALE,       t_Float},
137   { &max_tilt,      "maxTilt",      "MaxTilt",      DEF_MAX_TILT,    t_Float},
138   { &speed,         "speed",        "Speed",        DEF_SPEED,       t_Float},
139   { &duration,      "duration",     "Duration",     DEF_DURATION,    t_Int},
140   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,      t_Bool},
141   { &titles_p,      "titles",       "Titles",       DEF_TITLES,      t_Bool},
142   { &polaroid_p,    "polaroid",     "Polaroid",     DEF_POLAROID,    t_Bool},
143   { &clip_p,        "clip",         "Clip",         DEF_CLIP,        t_Bool},
144   { &shadows_p,     "shadows",      "Shadows",      DEF_SHADOWS,     t_Bool},
145   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,       t_Bool},
146 };
147
148 ENTRYPOINT ModeSpecOpt photopile_opts = {countof(opts), opts, countof(vars), vars, NULL};
149
150
151 /* Functions to interpolate between image positions.
152  */
153 static position
154 add_pos(position p, position q)
155 {
156   p.x += q.x;
157   p.y += q.y;
158   p.angle += q.angle;
159   return p;
160 }
161
162 static position
163 scale_pos(GLfloat t, position p)
164 {
165   p.x *= t;
166   p.y *= t;
167   p.angle *= t;
168   return p;
169 }
170
171 static position
172 linear_combination(GLfloat t, position p, position q)
173 {
174   return add_pos(scale_pos(1.0 - t, p), scale_pos(t, q));
175 }
176
177 static position
178 interpolate(GLfloat t, position p[4])
179 {
180   /* de Casteljau's algorithm, 4 control points */
181   position p10 = linear_combination(t, p[0], p[1]);
182   position p11 = linear_combination(t, p[1], p[2]);
183   position p12 = linear_combination(t, p[2], p[3]);
184
185   position p20 = linear_combination(t, p10, p11);
186   position p21 = linear_combination(t, p11, p12);
187
188   return linear_combination(t, p20, p21);
189 }
190
191 static position
192 offset_pos(position p, GLfloat th, GLfloat r)
193 {
194   p.x += cos(th) * r;
195   p.y += sin(th) * r;
196   p.angle = (frand(2.0) - 1.0) * max_tilt;
197   return p;
198 }
199
200 /* Calculate new positions for all images.
201  */
202 static void
203 set_new_positions(photopile_state *ss)
204 {
205   ModeInfo *mi = ss->mi;
206   int i;
207
208   for (i = 0; i < MI_COUNT(mi)+1; ++i)
209     {
210       image *frame = ss->frames + i;
211       GLfloat d = sqrt(frame->w*frame->w + frame->h*frame->h);
212       GLfloat leave = frand(M_PI * 2.0);
213       GLfloat enter = frand(M_PI * 2.0);
214
215       /* start position */
216       frame->pos[0] = frame->pos[3];
217
218       /* end position */
219       frame->pos[3].x = BELLRAND(MI_WIDTH(mi));
220       frame->pos[3].y = BELLRAND(MI_HEIGHT(mi));
221       frame->pos[3].angle = (frand(2.0) - 1.0) * max_tilt;
222
223       /* intermediate points */
224       frame->pos[1] = offset_pos(frame->pos[0], leave, d * (0.5 + frand(1.0)));
225       frame->pos[2] = offset_pos(frame->pos[3], enter, d * (0.5 + frand(1.0)));
226     }
227 }
228
229 /* Callback that tells us that the texture has been loaded.
230  */
231 static void
232 image_loaded_cb (const char *filename, XRectangle *geom,
233                  int image_width, int image_height,
234                  int texture_width, int texture_height,
235                  void *closure)
236 {
237   photopile_state *ss = (photopile_state *) closure;
238   ModeInfo *mi = ss->mi;
239   int wire = MI_IS_WIREFRAME(mi);
240   image *frame = ss->frames + ss->nframe;
241
242   if (wire)
243     {
244       if (random() % 2)
245         {
246           frame->w = (int)(MI_WIDTH(mi)  * scale) - 1;
247           frame->h = (int)(MI_HEIGHT(mi) * scale) - 1;
248         }
249       else
250         {
251           frame->w = (int)(MI_HEIGHT(mi) * scale) - 1;
252           frame->h = (int)(MI_WIDTH(mi)  * scale) - 1;
253         }
254       if (frame->w <= 10) frame->w = 10;
255       if (frame->h <= 10) frame->h = 10;
256       frame->geom.width  = frame->w;
257       frame->geom.height = frame->h;
258       goto DONE;
259     }
260
261   if (image_width == 0 || image_height == 0)
262     exit (1);
263
264   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
265   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
266                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
267
268   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
269   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
270
271   frame->w  = image_width;
272   frame->h  = image_height;
273   frame->tw = texture_width;
274   frame->th = texture_height;
275   frame->geom = *geom;
276
277   if (frame->title)
278     free (frame->title);
279   frame->title = (filename ? strdup (filename) : 0);
280
281 # if 0 /* xscreensaver-getimage returns paths relative to the image directory
282           now, so leave the sub-directory part in.
283         */
284   if (frame->title)   /* strip filename to part after last /. */
285     {
286       char *s = strrchr (frame->title, '/');
287       if (s) strcpy (frame->title, s+1);
288     }
289 # endif /* 0 */
290
291   if (debug_p)
292     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
293              progname,
294              frame->geom.width, 
295              frame->geom.height, 
296              frame->tw, frame->th,
297              (frame->title ? frame->title : "(null)"));
298
299  DONE:
300   frame->loaded_p = True;
301 }
302
303
304 /* Load a new file.
305  */
306 static void
307 load_image (ModeInfo *mi)
308 {
309   photopile_state *ss = &sss[MI_SCREEN(mi)];
310   int wire = MI_IS_WIREFRAME(mi);
311   image *frame = ss->frames + ss->nframe;
312
313   if (debug_p && !wire && frame->w != 0)
314     fprintf (stderr, "%s:  dropped %4d x %-4d  %4d x %-4d  \"%s\"\n",
315              progname, 
316              frame->geom.width, 
317              frame->geom.height, 
318              frame->tw, frame->th,
319              (frame->title ? frame->title : "(null)"));
320
321   frame->loaded_p = False;
322
323   if (wire)
324     image_loaded_cb (0, 0, 0, 0, 0, 0, ss);
325   else
326     {
327       int w = MI_WIDTH(mi);
328       int h = MI_HEIGHT(mi);
329       int size = (int)((w > h ? w : h) * scale);
330       if (size <= 10) size = 10;
331       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
332                           size, size,
333                           mipmap_p, frame->texid, 
334                           image_loaded_cb, ss);
335     }
336 }
337
338
339 static void
340 loading_msg (ModeInfo *mi)
341 {
342   photopile_state *ss = &sss[MI_SCREEN(mi)];
343   int wire = MI_IS_WIREFRAME(mi);
344   const char text[] = "Loading...";
345
346   if (wire) return;
347
348   if (ss->loading_sw == 0)    /* only do this once */
349     ss->loading_sw = texture_string_width (ss->texfont, text, &ss->loading_sh);
350
351   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
352
353   glMatrixMode(GL_PROJECTION);
354   glPushMatrix();
355   glLoadIdentity();
356
357   glMatrixMode(GL_MODELVIEW);
358   glPushMatrix();
359   glLoadIdentity();
360   gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
361
362   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
363                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
364                 0);
365   glColor3f (1, 1, 0);
366   glEnable (GL_TEXTURE_2D);
367   glDisable (GL_DEPTH_TEST);
368   print_texture_string (ss->texfont, text);
369   glEnable (GL_DEPTH_TEST);
370   glPopMatrix();
371
372   glMatrixMode(GL_PROJECTION);
373   glPopMatrix();
374
375   glMatrixMode(GL_MODELVIEW);
376
377   glFinish();
378   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
379 }
380
381
382 static Bool
383 loading_initial_image (ModeInfo *mi)
384 {
385   photopile_state *ss = &sss[MI_SCREEN(mi)];
386
387   if (ss->frames[ss->nframe].loaded_p)
388     {
389       /* The initial image has been fully loaded, start fading it in. */
390       int i;
391
392       for (i = 0; i < ss->nframe; ++i)
393         {
394           ss->frames[i].pos[3].x = MI_WIDTH(mi) * 0.5;
395           ss->frames[i].pos[3].y = MI_HEIGHT(mi) * 0.5;
396           ss->frames[i].pos[3].angle = 0.0;
397         }
398       set_new_positions(ss);
399
400       ss->mode = SHUFFLE;
401       ss->mode_tick = fade_ticks / speed;
402     }
403   else
404     {
405       loading_msg(mi);
406     }
407
408   return (ss->mode == EARLY);
409 }
410
411
412 ENTRYPOINT void
413 reshape_photopile (ModeInfo *mi, int width, int height)
414 {
415   glViewport (0, 0, (GLint) width, (GLint) height);
416
417   glMatrixMode(GL_PROJECTION);
418   glLoadIdentity();
419
420   glMatrixMode(GL_MODELVIEW);
421   glLoadIdentity();
422   gluOrtho2D(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi));
423
424   glClear(GL_COLOR_BUFFER_BIT);
425 }
426
427
428 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
429  */
430 static void
431 hack_resources (Display *dpy)
432 {
433 # ifndef HAVE_COCOA
434   char *res = "desktopGrabber";
435   char *val = get_string_resource (dpy, res, "DesktopGrabber");
436   char buf1[255];
437   char buf2[255];
438   XrmValue value;
439   XrmDatabase db = XtDatabase (dpy);
440   sprintf (buf1, "%.100s.%.100s", progname, res);
441   sprintf (buf2, "%.200s -v", val);
442   value.addr = buf2;
443   value.size = strlen(buf2);
444   XrmPutResource (&db, buf1, "String", &value);
445 # endif /* !HAVE_COCOA */
446 }
447
448
449 ENTRYPOINT void
450 init_photopile (ModeInfo *mi)
451 {
452   int screen = MI_SCREEN(mi);
453   photopile_state *ss;
454   int wire = MI_IS_WIREFRAME(mi);
455
456   if (sss == NULL) {
457     if ((sss = (photopile_state *)
458          calloc (MI_NUM_SCREENS(mi), sizeof(photopile_state))) == NULL)
459       return;
460   }
461   ss = &sss[screen];
462   ss->mi = mi;
463
464   if ((ss->glx_context = init_GL(mi)) != NULL) {
465     reshape_photopile (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
466     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
467   } else {
468     MI_CLEARWINDOW(mi);
469   }
470
471   glDisable (GL_LIGHTING);
472   glEnable (GL_DEPTH_TEST);
473   glDisable (GL_CULL_FACE);
474
475   if (! wire)
476     {
477       glShadeModel (GL_SMOOTH);
478       glEnable (GL_LINE_SMOOTH);
479       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
480       glEnable (GL_BLEND);
481       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
482     }
483
484   ss->shadow = init_drop_shadow();
485   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
486
487   if (debug_p)
488     hack_resources (MI_DISPLAY (mi));
489
490   ss->frames = (image *)calloc (MI_COUNT(mi) + 1, sizeof(image));
491   ss->nframe = 0;
492   if (!wire)
493     {
494       int i;
495       for (i = 0; i < MI_COUNT(mi) + 1; ++i)
496         {
497           glGenTextures (1, &(ss->frames[i].texid));
498           if (ss->frames[i].texid <= 0) abort();
499         }
500     }
501
502   ss->mode = EARLY;
503   load_image(mi); /* start loading the first image */
504 }
505
506
507 static void
508 draw_image (ModeInfo *mi, int i, GLfloat t, GLfloat s, GLfloat z)
509 {
510   int wire = MI_IS_WIREFRAME(mi);
511   photopile_state *ss = &sss[MI_SCREEN(mi)];
512   image *frame = ss->frames + i;
513
514   position pos = interpolate(t, frame->pos);
515   GLfloat w = frame->geom.width * 0.5;
516   GLfloat h = frame->geom.height * 0.5;
517   GLfloat z1 = z - 0.25 / (MI_COUNT(mi) + 1);
518   GLfloat z2 = z - 0.5  / (MI_COUNT(mi) + 1); 
519   GLfloat w1 = w;
520   GLfloat h1 = h;
521   GLfloat h2 = h;
522
523   if (polaroid_p)
524     {
525       GLfloat minSize = MIN(w, h);
526       GLfloat maxSize = MAX(w, h);
527
528       /* Clip or scale image to fit in the frame.
529        */
530       if (clip_p)
531         {
532           w = h = minSize;
533         }
534       else
535         {
536           GLfloat scale = minSize / maxSize;
537           w *= scale;
538           h *= scale;
539         }
540
541       w1 = minSize * 1.16;      /* enlarge frame border */
542       h1 = minSize * 1.5;
543       h2 = w1;
544       s /= 1.5;                 /* compensate for border size */
545     }
546
547   glPushMatrix();
548
549   /* Position and scale this image.
550    */
551   glTranslatef (pos.x, pos.y, 0);
552   glRotatef (pos.angle, 0, 0, 1);
553   glScalef (s, s, 1);
554
555   /* Draw the drop shadow. */
556   if (shadows_p && !wire)
557     {
558       glColor3f (0, 0, 0);
559       draw_drop_shadow(ss->shadow, -w1, -h1, z2, 2.0 * w1, h1 + h2, 20.0);
560     }
561
562   /* Draw the retro instant-film frame.
563    */
564   if (polaroid_p)
565     {
566       if (! wire)
567         {
568           glColor3f (1, 1, 1);
569           glBegin (GL_QUADS);
570           glVertex3f (-w1, -h1, z2);
571           glVertex3f ( w1, -h1, z2);
572           glVertex3f ( w1,  h2, z2);
573           glVertex3f (-w1,  h2, z2);
574           glEnd();
575         }
576
577       glLineWidth (2.0);
578       glColor3f (0.5, 0.5, 0.5);
579       glBegin (GL_LINE_LOOP);
580       glVertex3f (-w1, -h1, z);
581       glVertex3f ( w1, -h1, z);
582       glVertex3f ( w1,  h2, z);
583       glVertex3f (-w1,  h2, z);
584       glEnd();
585     }
586
587   /* Draw the image quad.
588    */
589   if (! wire)
590     {
591       GLfloat texw = w / frame->tw;
592       GLfloat texh = h / frame->th;
593       GLfloat texx = (frame->geom.x + 0.5 * frame->geom.width)  / frame->tw;
594       GLfloat texy = (frame->geom.y + 0.5 * frame->geom.height) / frame->th;
595
596       glBindTexture (GL_TEXTURE_2D, frame->texid);
597       glEnable (GL_TEXTURE_2D);
598       glColor3f (1, 1, 1);
599       glBegin (GL_QUADS);
600       glTexCoord2f (texx - texw, texy + texh); glVertex3f (-w, -h, z1);
601       glTexCoord2f (texx + texw, texy + texh); glVertex3f ( w, -h, z1);
602       glTexCoord2f (texx + texw, texy - texh); glVertex3f ( w,  h, z1);
603       glTexCoord2f (texx - texw, texy - texh); glVertex3f (-w,  h, z1);
604       glEnd();
605       glDisable (GL_TEXTURE_2D);
606     }
607
608   /* Draw a box around it.
609    */
610   glLineWidth (2.0);
611   glColor3f (0.5, 0.5, 0.5);
612   glBegin (GL_LINE_LOOP);
613   glVertex3f (-w, -h, z);
614   glVertex3f ( w, -h, z);
615   glVertex3f ( w,  h, z);
616   glVertex3f (-w,  h, z);
617   glEnd();
618
619   /* Draw a title under the image.
620    */
621   if (titles_p)
622     {
623       int sw, sh;
624       GLfloat scale = 0.6;
625       const char *title = frame->title ? frame->title : "(untitled)";
626
627       /* #### Highly approximate, but doing real clipping is harder... */
628       int max = 35;
629       if (strlen(title) > max)
630         title += strlen(title) - max;
631
632       sw = texture_string_width (ss->texfont, title, &sh);
633
634       glTranslatef (-sw*scale*0.5, -h - sh*scale, z);
635       glScalef (scale, scale, 1);
636
637       if (wire || !polaroid_p)
638         {
639           glColor3f (1, 1, 1);
640         }
641       else
642         {
643           glColor3f (0, 0, 0);
644         }
645
646       if (!wire)
647         {
648           glEnable (GL_TEXTURE_2D);
649           print_texture_string (ss->texfont, title);
650           glDisable (GL_TEXTURE_2D);
651         }
652       else
653         {
654           glBegin (GL_LINE_LOOP);
655           glVertex3f (0,  0,  0);
656           glVertex3f (sw, 0,  0);
657           glVertex3f (sw, sh, 0);
658           glVertex3f (0,  sh, 0);
659           glEnd();
660         }
661     }
662
663   glPopMatrix();
664 }
665
666
667 ENTRYPOINT void
668 draw_photopile (ModeInfo *mi)
669 {
670   photopile_state *ss = &sss[MI_SCREEN(mi)];
671   int i;
672
673   if (!ss->glx_context)
674     return;
675
676   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
677
678   if (ss->mode == EARLY)
679     if (loading_initial_image (mi))
680       return;
681
682   /* Only check the wall clock every 10 frames */
683   if (ss->now == 0 || ss->draw_tick++ > 10)
684     {
685       ss->now = time((time_t *) 0);
686       if (ss->last_time == 0) ss->last_time = ss->now;
687       ss->draw_tick = 0;
688     }
689
690   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
691   {
692     GLfloat t;
693
694     /* Handle state transitions. */
695     switch (ss->mode)
696       {
697       case IN:
698         if (--ss->mode_tick <= 0)
699           {
700             ss->mode = NORMAL;
701             ss->last_time = time((time_t *) 0);
702           }
703         break;
704       case NORMAL:
705         if (ss->now - ss->last_time > duration)
706           {
707             ss->mode = LOADING;
708             load_image(mi);
709           }
710         break;
711       case LOADING:
712         if (ss->frames[ss->nframe].loaded_p)
713           {
714             set_new_positions(ss);
715             ss->mode = SHUFFLE;
716             ss->mode_tick = fade_ticks / speed;
717           }
718         break;
719       case SHUFFLE:
720         if (--ss->mode_tick <= 0)
721           {
722             ss->nframe = (ss->nframe+1) % (MI_COUNT(mi)+1);
723
724             ss->mode = NORMAL;
725             ss->last_time = time((time_t *) 0);
726           }
727         break;
728       default:
729         abort();
730       }
731
732     t = 1.0 - ss->mode_tick / (fade_ticks / speed);
733     t = 0.5 * (1.0 - cos(M_PI * t));
734
735     /* Draw the images. */
736     for (i = 0; i < MI_COUNT(mi) + (ss->mode == SHUFFLE); ++i)
737       {
738         int j = (ss->nframe + i + 1) % (MI_COUNT(mi) + 1);
739
740         if (ss->frames[j].loaded_p)
741           {
742             GLfloat s = 1.0;
743             GLfloat z = (GLfloat)i / (MI_COUNT(mi) + 1);
744
745             switch (ss->mode)
746               {
747               case IN:
748                 s *= t;
749                 break;
750               case NORMAL:
751               case LOADING:
752                 t = 1.0;
753                 break;
754               case SHUFFLE:
755                 if (i == MI_COUNT(mi))
756                   {
757                     s *= t;
758                   }
759                 else if (i == 0)
760                   {
761                     s *= 1.0 - t;
762                   }
763                 break;
764               default:
765                 abort();
766               }
767
768             draw_image(mi, j, t, s, z);
769           }
770       }
771   }
772
773   if (mi->fps_p) do_fps (mi);
774   glFinish();
775   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
776 }
777
778 XSCREENSAVER_MODULE ("Photopile", photopile)
779
780 #endif /* USE_GL */