1 /* esper, Copyright (c) 2017 Jamie Zawinski <jwz@jwz.org>
2 * Enhance 224 to 176. Pull out track right. Center in pull back.
3 * Pull back. Wait a minute. Go right. Stop. Enhance 57 19. Track 45 left.
4 * Gimme a hardcopy right there.
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation. No representations are made about the suitability of this
11 * software for any purpose. It is provided "as is" without express or
16 The Esper machine has a 4:3 display, about 12" diagonal.
17 The display is overlayed with a 10x7 grid of blue lines.
18 The scene goes approximately like this:
22 ZM 0000 NS 0000 EW 0000
24 The reticle is displayed centered.
25 It moves in 8 steps with 3 frame blur to move to around to grid 1,4.
27 ZM 0000 NS 0000 EW 0000
28 ZM 0000 NS 0001 EW 0001
29 ZM 0000 NS 0001 EW 0002
30 ZM 0000 NS 0002 EW 0003
31 ZM 0000 NS 0003 EW 0005
32 ZM 0000 NS 0004 EW 0008
33 ZM 0000 NS 0015 EW 0011
35 These numbers appear to have little relation to what we are
36 actually seeing on the screen. Also the same numbers are
37 repeated later when looking at totally different parts of
40 ZM 0000 NS 0117 EW 0334
42 The box appears: 8 steps, final box is 1.5x2.25 at -0.5,4.0.
44 ZM 4086 NS 0117 EW 0334
46 The box blinks yellow 5x.
47 The image's zoom-and-pan takes 8 steps, with no text on the screen.
48 The zoom is in discreet steps, with flashes.
49 The grid stays the same size the whole time.
50 The flashes look like solarization to blue.
51 When the zoom is finished, there is still no text.
53 "Enhance." Goes 4 more ticks down the same hole?
54 "Stop." Moves up a little bit at the end.
56 Then with no instructions, it goes 20 ticks by itself, off camera.
59 "Stop." (We are looking at a fist in the picture.)
60 "Pull out track right."
61 "Stop." (We are looking at a newspaper.)
62 "Center and pull back."
63 "Stop." (We just passed the round mirror.)
68 This time there was no grid until it stopped, then the grid showed up.
69 There is video tearing at the bottom.
73 ZM 0000 NS 0063 EW 0185
74 ZM 0000 NS 0197 EW 0334
75 ZM 3841 NS 0197 EW 0334
77 It kind of zooms in to the center wobbly and willy-nilly.
78 We are now looking at a glass.
80 "Pan right and pull back." (There is no grid while moving again.)
83 Ok, at this point, we enter fantasy-land. From here on, the images
84 shown are very high resolution with no noise. And suddenly the
85 UI on the Esper is *way* higher resolution. My theory is that from
86 this point on in the scene, we are not looking at the literal Esper
87 machine, but instead the movie is presenting Decard's perception of
88 it. We're seeing the room, not the photo of the room. The map has
93 ZM 0000 NS 0197 EW 0334
95 This has the reticle and box only, no grid, ends with no grid.
98 "Wait a minute. Go right."
100 Now it's going around the corner or something.
103 This has a reticle then box, but the image started zooming early.
106 zooms out and moves left
108 "Stop." (O hai Zhora.)
111 ZM 3852 NS 0197 EW 0334
113 "Gimme a hardcopy right there."
115 The printer polaroid is WAY lower resolution than the image we see on
116 the "screen" -- in keeping with my theory that we were not seeing the
122 * There's a glitch at the top/bottom of the texfont textures.
123 * "Pull back" isn't quite symmetric: zoom origin is slightly off.
124 * Maybe display text like "Pull right" and "Stop".
128 /* Use a small point size to keep it nice and grainy. */
129 #if defined(HAVE_COCOA) || defined(HAVE_ANDROID)
130 # define TITLE_FONT "OCR A Std 10, Lucida Console 10, Monaco 10"
132 # define TITLE_FONT "-*-courier-bold-r-*-*-*-100-*-*-m-*-*-*"
135 #define DEFAULTS "*delay: 20000 \n" \
136 "*wireframe: False \n" \
137 "*showFPS: False \n" \
140 "*titleFont: " TITLE_FONT "\n" \
141 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
142 "*grabDesktopImages: False \n" \
143 "*chooseRandomImages: True \n" \
144 "*gridColor: #4444FF\n" \
145 "*reticleColor: #FFFF77\n" \
146 "*textColor: #FFFFBB\n" \
148 # define free_esper 0
149 # define refresh_esper 0
150 # define release_esper 0
151 # include "xlockmore.h"
154 #define countof(x) (sizeof((x))/sizeof((*x)))
157 #define RANDSIGN() ((random() & 1) ? 1 : -1)
159 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
165 # define DEF_GRID_SIZE "11"
166 # define DEF_GRID_THICKNESS "15"
167 # define DEF_TITLES "True"
168 # define DEF_SPEED "1.0"
169 # define DEF_DEBUG "False"
171 #include "grab-ximage.h"
174 #ifdef HAVE_XSHM_EXTENSION
175 # include "xshm.h" /* to get <sys/shm.h> */
185 unsigned long id; /* unique */
186 char *title; /* the filename of this image */
187 int w, h; /* size in pixels of the image */
188 int tw, th; /* size in pixels of the texture */
189 XRectangle geom; /* where in the image the bits are */
190 Bool loaded_p; /* whether the image has finished loading */
191 Bool used_p; /* whether the image has yet appeared
193 GLuint texid; /* which texture contains the image */
194 int refcount; /* how many sprites refer to this image */
215 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
216 typedef enum { IMAGE, RETICLE, BOX, GRID, FLASH, TEXT } sprite_type;
219 unsigned long id; /* unique */
221 image *img; /* type = IMAGE */
222 unsigned long text_id; /* type = TEXT */
225 GLfloat thickness_scale; /* line and image types */
227 double start_time; /* when this animation began */
228 double duration; /* lifetime of sprite in seconds; 0 = inf */
229 double fade_duration; /* speed of fade in and fade out */
230 double pause_duration; /* delay before fade-in starts */
231 Bool remain_p; /* pause forever before fade-out */
232 rect from, to, current; /* the journey this image is taking */
233 sprite_state state; /* the state we're in right now */
234 double state_time; /* time of last state change */
235 int frame_count; /* frames since last state change */
236 Bool fatbits_p; /* For image texture rendering */
237 Bool back_p; /* If BOX, zooming out, not in */
242 GLXContext *glx_context;
243 int nimages; /* how many images are loaded or loading now */
244 image *images[10]; /* pointers to the images */
246 int nsprites; /* how many sprites are animating right now */
247 sprite *sprites[100]; /* pointers to the live sprites */
249 double now; /* current time in seconds */
250 double dawn_of_time; /* when the program launched */
251 double image_load_time; /* time when we last loaded a new image */
253 texture_font_data *font_data;
255 int sprite_id, image_id; /* debugging id counters */
257 GLfloat grid_color[4], reticle_color[4], text_color[4];
259 anim_state anim_state; /* Counters for global animation state, */
260 double anim_start, anim_duration;
266 static esper_state *sss = NULL;
269 /* Command-line arguments
271 static int grid_size;
272 static int grid_thickness;
274 static Bool do_titles; /* Display image titles. */
275 static GLfloat speed;
276 static Bool debug_p; /* Be loud and do weird things. */
279 static XrmOptionDescRec opts[] = {
280 { "-speed", ".speed", XrmoptionSepArg, 0 },
281 { "-titles", ".titles", XrmoptionNoArg, "True" },
282 { "-no-titles", ".titles", XrmoptionNoArg, "False" },
283 { "-debug", ".debug", XrmoptionNoArg, "True" },
286 static argtype vars[] = {
287 { &grid_size, "gridSize", "GridSize", DEF_GRID_SIZE, t_Int},
288 { &grid_thickness,"gridThickness","GridThickness",DEF_GRID_THICKNESS, t_Int},
289 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
290 { &speed, "speed", "Speed", DEF_SPEED, t_Float},
291 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
294 ENTRYPOINT ModeSpecOpt esper_opts = {countof(opts), opts, countof(vars), vars, NULL};
297 /* Returns the current time in seconds as a double.
303 # ifdef GETTIMEOFDAY_TWO_ARGS
305 gettimeofday(&now, &tzp);
310 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
314 state_name (anim_state s)
317 case BLANK: return "BLANK";
318 case GRID_ON: return "GRID_ON";
319 case IMAGE_LOAD: return "IMAGE_LOAD";
320 case IMAGE_UNLOAD: return "IMAGE_UNLOAD";
321 case IMAGE_FORCE_UNLOAD: return "IMAGE_FORCE_UNLOAD";
322 case REPOSITION: return "REPOSITION";
323 case RETICLE_ON: return "RETICLE_ON";
324 case RETICLE_MOVE: return "RETICLE_MOVE";
325 case BOX_MOVE: return "BOX_MOVE";
326 case IMAGE_ZOOM: return "IMAGE_ZOOM";
327 case MANUAL_BOX_ON: return "MANUAL_BOX_ON";
328 case MANUAL_BOX: return "MANUAL_BOX";
329 case MANUAL_RETICLE_ON: return "MANUAL_RETICLE_ON";
330 case MANUAL_RETICLE: return "MANUAL_RETICLE";
331 default: return "UNKNOWN";
336 static void image_loaded_cb (const char *filename, XRectangle *geom,
337 int image_width, int image_height,
338 int texture_width, int texture_height,
342 /* Allocate an image structure and start a file loading in the background.
345 alloc_image (ModeInfo *mi)
347 esper_state *ss = &sss[MI_SCREEN(mi)];
348 int wire = MI_IS_WIREFRAME(mi);
349 image *img = (image *) calloc (1, sizeof (*img));
351 img->id = ++ss->image_id;
352 img->loaded_p = False;
356 glGenTextures (1, &img->texid);
357 if (img->texid <= 0) abort();
359 ss->image_load_time = ss->now;
362 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
365 /* If possible, load images at much higher resolution than the window,
366 to facilitate deep zooms.
368 int max_max = 4096; /* ~12 megapixels */
371 # if defined(HAVE_XSHM_EXTENSION) && \
372 !defined(HAVE_MOBILE) && \
375 /* Try not to ask for an image larger than the SHM segment size.
376 If XSHM fails in a real-X11 world, it can take a staggeringly long
377 time to transfer the image bits from the server over Xproto -- like,
378 *18 seconds* for 4096 px and 8 seconds for 3072 px on MacOS XQuartz.
379 What madness is this?
381 unsigned long shmmax = 0;
384 /* Linux 2.6 defines this to be 0x2000000, but on CentOS 6.9,
385 "sysctl kernel.shmmax" reports a luxurious 0x1000000000. */
387 # elif defined(__APPLE__)
388 /* MacOS 10.13 "sysctl kern.sysv.shmmax" is paltry: */
390 # endif /* !SHMMAX */
394 /* Roughly, bytes => NxN. b = (n/8)*4n = n*n*4, so n^2 = 2b, so: */
395 unsigned long n = sqrt(shmmax)/2;
399 # endif /* HAVE_XSHM_EXTENSION and real X11 */
401 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
402 if (max > max_max) max = max_max;
404 /* Never ask for an image smaller than the window, even if that
405 will make XSHM fall back to Xproto. */
406 if (max < MI_WIDTH(mi) || max < MI_HEIGHT(mi))
409 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
410 max, max, False, img->texid, image_loaded_cb, img);
413 ss->images[ss->nimages++] = img;
414 if (ss->nimages >= countof(ss->images)) abort();
420 /* Callback that tells us that the texture has been loaded.
423 image_loaded_cb (const char *filename, XRectangle *geom,
424 int image_width, int image_height,
425 int texture_width, int texture_height,
428 image *img = (image *) closure;
429 ModeInfo *mi = img->mi;
431 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
433 int wire = MI_IS_WIREFRAME(mi);
437 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
438 img->h = MI_HEIGHT (mi);
439 img->geom.width = img->w;
440 img->geom.height = img->h;
444 if (image_width == 0 || image_height == 0)
447 img->w = image_width;
448 img->h = image_height;
449 img->tw = texture_width;
450 img->th = texture_height;
452 img->title = (filename ? strdup (filename) : 0);
454 ow = img->geom.width;
455 oh = img->geom.height;
457 /* If the image's width doesn't come back as the width of the screen,
458 then the image must have been scaled down (due to insufficient
459 texture memory.) Scale up the coordinates to stretch the image
462 if (img->w != MI_WIDTH(mi))
464 double scale = (double) MI_WIDTH(mi) / img->w;
469 img->geom.x *= scale;
470 img->geom.y *= scale;
471 img->geom.width *= scale;
472 img->geom.height *= scale;
475 /* xscreensaver-getimage returns paths relative to the image directory
476 now, so leave the sub-directory part in. Unless it's an absolute path.
478 if (img->title && img->title[0] == '/')
480 /* strip filename to part between last "/" and last ".". */
481 char *s = strrchr (img->title, '/');
482 if (s) strcpy (img->title, s+1);
483 s = strrchr (img->title, '.');
487 # if !(__APPLE__ && TARGET_IPHONE_SIMULATOR || !defined(__OPTIMIZE__))
490 fprintf (stderr, "%s: loaded %lu \"%s\" %dx%d\n",
491 progname, img->id, (img->title ? img->title : "(null)"),
495 img->loaded_p = True;
500 /* Free the image and texture, after nobody is referencing it.
503 destroy_image (ModeInfo *mi, image *img)
505 esper_state *ss = &sss[MI_SCREEN(mi)];
506 Bool freed_p = False;
510 if (!img->loaded_p) abort();
511 if (!img->used_p) abort();
512 if (img->texid <= 0) abort();
513 if (img->refcount != 0) abort();
515 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
516 if (ss->images[i] == img)
519 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
520 ss->images[j] = ss->images[j+1];
527 if (!freed_p) abort();
530 fprintf (stderr, "%s: unloaded img %2lu: \"%s\"\n",
531 progname, img->id, (img->title ? img->title : "(null)"));
533 if (img->title) free (img->title);
534 glDeleteTextures (1, &img->texid);
539 /* Return an image to use for a sprite.
540 If it's time for a new one, get a new one.
541 Otherwise, use an old one.
542 Might return 0 if the machine is really slow.
545 get_image (ModeInfo *mi)
547 esper_state *ss = &sss[MI_SCREEN(mi)];
549 image *loading_img = 0;
552 for (i = 0; i < ss->nimages; i++)
554 image *img2 = ss->images[i];
562 /* Make sure that there is always one unused image in the pipe.
564 if (!img && !loading_img)
571 /* Allocate a new sprite and start its animation going.
574 new_sprite (ModeInfo *mi, sprite_type type)
576 esper_state *ss = &sss[MI_SCREEN(mi)];
577 image *img = (type == IMAGE ? get_image (mi) : 0);
580 if (type == IMAGE && !img)
582 /* Oops, no images yet! The machine is probably hurting bad.
583 Let's give it some time before thrashing again. */
588 sp = (sprite *) calloc (1, sizeof (*sp));
589 sp->id = ++ss->sprite_id;
591 sp->start_time = ss->now;
592 sp->state_time = sp->start_time;
593 sp->thickness_scale = 1;
604 sp->img->used_p = True;
605 sp->duration = 0; /* forever, until further notice */
606 sp->fade_duration = 0.5;
608 /* Scale the sprite so that the image bits fill the window. */
610 double w = MI_WIDTH(mi);
611 double h = MI_HEIGHT(mi);
613 r = ((img->geom.height / (double) img->geom.width) * (w / h));
620 /* Pan to a random spot */
622 sp->to.y += frand ((sp->to.h - 1) / 2) * RANDSIGN();
624 sp->to.x += frand ((sp->to.w - 1) / 2) * RANDSIGN();
627 sp->from = sp->current = sp->to;
629 ss->sprites[ss->nsprites++] = sp;
630 if (ss->nsprites >= countof(ss->sprites)) abort();
637 copy_sprite (ModeInfo *mi, sprite *old)
639 sprite *sp = new_sprite (mi, (sprite_type) ~0L);
641 double tt = sp->start_time;
644 memcpy (sp, old, sizeof(*sp));
647 sp->state_time = sp->start_time = tt;
654 /* Free the given sprite, and decrement the reference count on its image.
657 destroy_sprite (ModeInfo *mi, sprite *sp)
659 esper_state *ss = &sss[MI_SCREEN(mi)];
660 Bool freed_p = False;
665 if (sp->state != DEAD) abort();
668 if (sp->type != IMAGE)
675 if (!img->loaded_p) abort();
676 if (!img->used_p) abort();
677 if (img->refcount <= 0) abort();
680 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
681 if (ss->sprites[i] == sp)
684 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
685 ss->sprites[j] = ss->sprites[j+1];
692 if (!freed_p) abort();
693 if (sp->text) free (sp->text);
700 if (img->refcount < 0) abort();
701 if (img->refcount == 0)
702 destroy_image (mi, img);
707 /* Updates the sprite for the current frame of the animation based on
708 its creation time compared to the current wall clock.
711 tick_sprite (ModeInfo *mi, sprite *sp)
713 esper_state *ss = &sss[MI_SCREEN(mi)];
714 image *img = sp->img;
715 double now = ss->now;
718 GLfloat visible = sp->duration + sp->fade_duration * 2;
719 GLfloat total = sp->pause_duration + visible;
721 if (sp->type != IMAGE)
723 if (sp->img) abort();
727 if (! sp->img) abort();
728 if (! img->loaded_p) abort();
731 /* pause fade duration fade
732 |------------|------------|---------|-----------|
733 ....----====##########====----....
737 secs = now - sp->start_time;
738 ratio = (visible <= 0 ? 1 : ((secs - sp->pause_duration) / visible));
739 if (ratio < 0) ratio = 0;
740 else if (ratio > 1) ratio = 1;
742 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
743 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
744 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
745 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
747 sp->thickness_scale = 1;
749 if (secs < sp->pause_duration)
754 else if (secs < sp->pause_duration + sp->fade_duration)
757 sp->opacity = (secs - sp->pause_duration) / (GLfloat) sp->fade_duration;
759 else if (sp->duration == 0 || /* 0 means infinite lifetime */
761 secs < sp->pause_duration + sp->fade_duration + sp->duration)
766 /* Just after reaching full opacity, pulse the width up and down. */
767 if (sp->fade_duration > 0 &&
768 secs < sp->pause_duration + sp->fade_duration * 2)
770 GLfloat f = ((secs - (sp->pause_duration + sp->fade_duration)) /
773 sp->thickness_scale = 1 + 3 * (f > 0.5 ? 1-f : f);
776 else if (secs < total)
779 sp->opacity = (total - secs) / sp->fade_duration;
791 /* Draw the given sprite at the phase of its animation dictated by
792 its creation time compared to the current wall clock.
795 draw_image_sprite (ModeInfo *mi, sprite *sp)
797 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
798 int wire = MI_IS_WIREFRAME(mi);
799 image *img = sp->img;
801 if (! sp->img) abort();
802 if (! img->loaded_p) abort();
806 GLfloat s = 1 + (sp->thickness_scale - 1) / 40.0;
807 glTranslatef (0.5, 0.5, 0);
809 glTranslatef (-0.5, -0.5, 0);
811 glTranslatef (sp->current.x, sp->current.y, 0);
812 glScalef (sp->current.w, sp->current.h, 1);
814 glTranslatef (-0.5, -0.5, 0);
816 if (wire) /* Draw a grid inside the box */
819 GLfloat dx = dy * img->w / img->h;
823 glColor4f (sp->opacity, 0, 0, 1);
825 glColor4f (0, 0, sp->opacity, 1);
828 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
829 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
831 for (y = 0; y < 1+dy; y += dy)
833 GLfloat yy = (y > 1 ? 1 : y);
834 for (x = 0.5; x < 1+dx; x += dx)
836 GLfloat xx = (x > 1 ? 1 : x);
837 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
838 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
840 for (x = 0.5; x > -dx; x -= dx)
842 GLfloat xx = (x < 0 ? 0 : x);
843 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
844 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
849 else /* Draw the texture quad */
851 GLfloat texw = img->geom.width / (GLfloat) img->tw;
852 GLfloat texh = img->geom.height / (GLfloat) img->th;
853 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
854 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
855 GLfloat texx2 = texx1 + texw;
856 GLfloat texy2 = texy1 + texh;
857 GLfloat o = sp->opacity;
858 GLint mag = (sp->fatbits_p ? GL_NEAREST : GL_LINEAR);
860 glBindTexture (GL_TEXTURE_2D, img->texid);
862 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
863 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
864 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
865 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mag);
867 /* o = 1 - sin ((1 - o*o*o) * M_PI/2); */
868 glColor4f (1, 1, 1, o);
870 glNormal3f (0, 0, 1);
872 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
873 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
874 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
875 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
878 if (debug_p) /* Draw a border around the image */
880 if (!wire) glDisable (GL_TEXTURE_2D);
881 glColor4f (sp->opacity, 0, 0, 1);
882 glBegin (GL_LINE_LOOP);
883 glVertex3f (0, 0, 0);
884 glVertex3f (0, 1, 0);
885 glVertex3f (1, 1, 0);
886 glVertex3f (1, 0, 0);
888 if (!wire) glEnable (GL_TEXTURE_2D);
897 draw_line_sprite (ModeInfo *mi, sprite *sp)
899 esper_state *ss = &sss[MI_SCREEN(mi)];
900 int wire = MI_IS_WIREFRAME(mi);
901 int w = MI_WIDTH(mi);
902 int h = MI_HEIGHT(mi);
903 int wh = (w > h ? w : h);
904 int gs = (sp->type == RETICLE ? grid_size+1 : grid_size);
905 int sx = wh / (gs + 1);
908 GLfloat t = grid_thickness * sp->thickness_scale;
912 GLfloat x = w * sp->current.x;
913 GLfloat y = h * sp->current.y;
914 GLfloat bw = w * sp->current.w;
915 GLfloat bh = h * sp->current.h;
917 if (sx < 10) sx = 10;
920 if (t > sx/3) t = sx/3;
923 if (fade < 1) fade = 1;
925 if (t <= 0 || sp->opacity <= 0) return;
936 glOrtho (0, w, 0, h, -1, 1);
939 case GRID: memcpy (color, ss->grid_color, sizeof(color)); break;
940 case RETICLE: memcpy (color, ss->reticle_color, sizeof(color)); break;
941 case BOX: memcpy (color, ss->reticle_color, sizeof(color)); break;
945 if (sp->type == GRID)
947 GLfloat s = 1 + (sp->thickness_scale - 1) / 120.0;
948 glTranslatef (w/2, h/2, 0);
950 glTranslatef (-w/2, -h/2, 0);
955 if (!wire) glDisable (GL_TEXTURE_2D);
957 for (k = 0; k < fade; k++)
959 GLfloat t2 = t * (1 - (k / (fade * 1.0)));
961 color[3] = sp->opacity / fade;
964 glBegin (wire ? GL_LINES : GL_QUADS);
969 GLfloat xoff = (w - sx * (w / sx)) / 2.0;
970 GLfloat yoff = (h - sy * (h / sy)) / 2.0;
971 for (y = -sy/2+t2/2; y < h; y += sy)
972 for (x = -sx/2-t2/2; x < w; x += sx)
974 glVertex3f (xoff+x+t2, yoff+y, 0);
975 glVertex3f (xoff+x+t2, yoff+y+sy-t2, 0);
976 glVertex3f (xoff+x, yoff+y+sy-t2, 0);
977 glVertex3f (xoff+x, yoff+y, 0);
980 glVertex3f (xoff+x, yoff+y-t2, 0);
981 glVertex3f (xoff+x+sx, yoff+y-t2, 0);
982 glVertex3f (xoff+x+sx, yoff+y, 0);
983 glVertex3f (xoff+x, yoff+y, 0);
990 glVertex3f (x-bw/2-t2/2, y-bh/2-t2/2, 0);
991 glVertex3f (x+bw/2+t2/2, y-bh/2-t2/2, 0);
992 glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
993 glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
996 glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
997 glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
998 glVertex3f (x+bw/2+t2/2, y+bh/2+t2/2, 0);
999 glVertex3f (x-bw/2-t2/2, y+bh/2+t2/2, 0);
1000 mi->polygon_count++;
1002 glVertex3f (x-bw/2+t2/2, y-bh/2+t2/2, 0);
1003 glVertex3f (x-bw/2+t2/2, y+bh/2-t2/2, 0);
1004 glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
1005 glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
1006 mi->polygon_count++;
1008 glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
1009 glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
1010 glVertex3f (x+bw/2-t2/2, y+bh/2-t2/2, 0);
1011 glVertex3f (x+bw/2-t2/2, y-bh/2+t2/2, 0);
1012 mi->polygon_count++;
1016 glVertex3f (x+t2/2, y+sy/2-t2/2, 0);
1017 glVertex3f (x+t2/2, h, 0);
1018 glVertex3f (x-t2/2, h, 0);
1019 glVertex3f (x-t2/2, y+sy/2-t2/2, 0);
1020 mi->polygon_count++;
1022 glVertex3f (x-t2/2, y-sy/2+t2/2, 0);
1023 glVertex3f (x-t2/2, 0, 0);
1024 glVertex3f (x+t2/2, 0, 0);
1025 glVertex3f (x+t2/2, y-sy/2+t2/2, 0);
1026 mi->polygon_count++;
1028 glVertex3f (x-sx/2+t2/2, y+t2/2, 0);
1029 glVertex3f (0, y+t2/2, 0);
1030 glVertex3f (0, y-t2/2, 0);
1031 glVertex3f (x-sx/2+t2/2, y-t2/2, 0);
1032 mi->polygon_count++;
1034 glVertex3f (x+sx/2-t2/2, y-t2/2, 0);
1035 glVertex3f (w, y-t2/2, 0);
1036 glVertex3f (w, y+t2/2, 0);
1037 glVertex3f (x+sx/2-t2/2, y+t2/2, 0);
1038 mi->polygon_count++;
1048 if (!wire) glEnable (GL_TEXTURE_2D);
1052 static sprite * find_newest_sprite (ModeInfo *, sprite_type);
1053 static void compute_image_rect (rect *, sprite *, Bool);
1056 draw_text_sprite (ModeInfo *mi, sprite *sp)
1058 esper_state *ss = &sss[MI_SCREEN(mi)];
1059 int wire = MI_IS_WIREFRAME(mi);
1060 GLfloat w = MI_WIDTH(mi);
1061 GLfloat h = MI_HEIGHT(mi);
1070 if (sp->opacity <= 0)
1073 for (i = 0; i < ss->nsprites; i++)
1075 sprite *sp2 = ss->sprites[i];
1076 if (sp2->id == sp->text_id && sp2->state != DEAD)
1088 if (target->opacity <= 0 &&
1089 (target->state == NEW || target->state == IN))
1092 r = target->current;
1094 img = find_newest_sprite (mi, IMAGE);
1096 compute_image_rect (&r, img, target->back_p);
1098 mi->recursion_depth = (img
1099 ? MIN (img->current.w, img->current.h)
1102 x = abs ((int) (r.x * 10000)) % 10000;
1103 y = abs ((int) (r.y * 10000)) % 10000;
1104 z = abs ((int) (r.w * 10000)) % 10000;
1106 sprintf (text, "ZM %04d NS %04d EW %04d", z, y, x);
1108 if ((x == 0 || x == 5000) && /* startup */
1109 (y == 0 || y == 5000) &&
1110 (z == 0 || z == 5000))
1114 target->type == IMAGE &&
1115 target->remain_p) /* The initial background image */
1117 char *s = (target->img &&
1118 target->img->title && *target->img->title
1119 ? target->img->title
1122 int i = (L > 23 ? L-23 : 0);
1123 sprintf (text, ">>%-23s", target->img->title + i);
1124 for (s = text; *s; s++)
1125 if (*s >= 'a' && *s <= 'z') *s += ('A'-'a');
1126 else if (*s == '/' || *s == '-' || *s == '.') *s = '_';
1131 if (sp->text) free (sp->text);
1132 sp->text = strdup (text);
1134 else if (sp->text && *sp->text)
1135 /* The target sprite might be dead, but we saved our last text. */
1136 strcpy (text, sp->text);
1138 /* No target, no saved text. */
1141 texture_string_metrics (ss->font_data, text, &e, 0, 0);
1145 glOrtho (0, 1, 0, 1, -1, 1);
1147 /* Scale the text to fit N characters horizontally. */
1151 # else /* desktop */
1152 GLfloat c = (MI_WIDTH(mi) <= 640 ? 25 :
1153 MI_WIDTH(mi) <= 1280 ? 32 : 64);
1155 s = w / (e.ascent * c);
1159 x = (w - e.width) / 2;
1160 y = e.ascent + e.descent * 2;
1162 glScalef (1.0/w, 1.0/h, 1);
1163 glTranslatef (x, y, 0);
1165 memcpy (color, ss->text_color, sizeof(color));
1166 color[3] = sp->opacity;
1170 glEnable (GL_TEXTURE_2D);
1172 print_texture_string (ss->font_data, text);
1173 mi->polygon_count++;
1176 glDisable (GL_TEXTURE_2D);
1182 draw_flash_sprite (ModeInfo *mi, sprite *sp)
1184 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1185 GLfloat o = sp->opacity;
1188 o = 0.7; /* Too fast to see, so keep it consistent */
1191 int wire = MI_IS_WIREFRAME(mi);
1193 glDisable (GL_TEXTURE_2D);
1194 glColor4f (0, 0, 1, o);
1195 glColorMask (0, 0, 1, 1); /* write only into blue and alpha channels */
1197 glVertex3f (0, 0, 0);
1198 glVertex3f (1, 0, 0);
1199 glVertex3f (1, 1, 0);
1200 glVertex3f (0, 1, 0);
1202 glColorMask (1, 1, 1, 1);
1204 glEnable (GL_TEXTURE_2D);
1210 draw_sprite (ModeInfo *mi, sprite *sp)
1214 draw_image_sprite (mi, sp);
1219 draw_line_sprite (mi, sp);
1222 draw_text_sprite (mi, sp);
1225 draw_flash_sprite (mi, sp);
1234 tick_sprites (ModeInfo *mi)
1236 esper_state *ss = &sss[MI_SCREEN(mi)];
1238 for (i = 0; i < ss->nsprites; i++)
1239 tick_sprite (mi, ss->sprites[i]);
1241 for (i = 0; i < ss->nsprites; i++)
1243 sprite *sp = ss->sprites[i];
1244 if (sp->state == DEAD)
1246 destroy_sprite (mi, sp);
1254 draw_sprites (ModeInfo *mi)
1256 esper_state *ss = &sss[MI_SCREEN(mi)];
1259 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1265 GLfloat rot = current_device_rotation();
1266 glTranslatef (0.5, 0.5, 0);
1267 glRotatef(rot, 0, 0, 1);
1268 if ((rot > 45 && rot < 135) ||
1269 (rot < -45 && rot > -135))
1271 GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
1272 glScalef (s, 1/s, 1);
1274 glTranslatef (-0.5, -0.5, 0);
1278 /* Draw the images first, then the overlays. */
1279 for (i = 0; i < ss->nsprites; i++)
1280 if (ss->sprites[i]->type == IMAGE)
1281 draw_sprite (mi, ss->sprites[i]);
1282 for (i = 0; i < ss->nsprites; i++)
1283 if (ss->sprites[i]->type != IMAGE)
1284 draw_sprite (mi, ss->sprites[i]);
1288 if (debug_p) /* draw a white box (the "screen") */
1290 int wire = MI_IS_WIREFRAME(mi);
1292 if (!wire) glDisable (GL_TEXTURE_2D);
1294 glColor4f (1, 1, 1, 1);
1295 glBegin (GL_LINE_LOOP);
1296 glVertex3f (0, 0, 0);
1297 glVertex3f (0, 1, 0);
1298 glVertex3f (1, 1, 0);
1299 glVertex3f (1, 0, 0);
1302 if (!wire) glEnable (GL_TEXTURE_2D);
1308 fadeout_sprite (ModeInfo *mi, sprite *sp)
1310 esper_state *ss = &sss[MI_SCREEN(mi)];
1312 /* If it hasn't faded in yet, don't fade out. */
1313 if (ss->now <= sp->start_time + sp->pause_duration)
1314 sp->fade_duration = 0;
1316 /* Pretend it's at the point where it should fade out. */
1317 sp->pause_duration = 0;
1318 sp->duration = 9999;
1319 sp->remain_p = False;
1320 sp->start_time = ss->now - sp->duration;
1324 fadeout_sprites (ModeInfo *mi, sprite_type type)
1326 esper_state *ss = &sss[MI_SCREEN(mi)];
1328 for (i = 0; i < ss->nsprites; i++)
1330 sprite *sp = ss->sprites[i];
1331 if (sp->type == type)
1332 fadeout_sprite (mi, sp);
1338 find_newest_sprite (ModeInfo *mi, sprite_type type)
1340 esper_state *ss = &sss[MI_SCREEN(mi)];
1343 for (i = 0; i < ss->nsprites; i++)
1345 sprite *sp2 = ss->sprites[i];
1346 if (sp2->type == type &&
1348 (sp->start_time < sp2->start_time &&
1349 ss->now >= sp2->start_time + sp2->pause_duration)))
1356 /* Enqueue a text sprite describing the given sprite that runs at the
1360 push_text_sprite (ModeInfo *mi, sprite *sp)
1362 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1363 sprite *sp2 = new_sprite (mi, TEXT);
1365 sp2->text_id = sp->id;
1366 sp2->fade_duration = sp->fade_duration;
1367 sp2->duration = sp->duration;
1368 sp2->pause_duration = sp->pause_duration;
1373 /* Enqueue a flash sprite that fires at the same time.
1377 push_flash_sprite (ModeInfo *mi, sprite *sp)
1379 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1380 sprite *sp2 = new_sprite (mi, FLASH);
1382 if (sp->type != IMAGE) abort();
1383 sp2->text_id = sp->id;
1384 sp2->duration = MAX (0.07 / speed, 0.07);
1385 sp2->fade_duration = 0; /* Fading these is too fast to see */
1386 sp2->pause_duration = sp->pause_duration + (sp->fade_duration * 0.3);
1389 #endif /* !SMOOTH */
1392 /* Set the sprite's duration based on distance travelled.
1395 compute_sprite_duration (ModeInfo *mi, sprite *sp, Bool blink_p)
1397 /* Compute max distance traveled by any point (corners or center). */
1398 /* (cpp is the devil) */
1399 # define L(F) (sp->F.x - sp->F.w/2) /* delta of left edge, from/to */
1400 # define R(F) (1-(sp->F.x + sp->F.w/2)) /* right */
1401 # define B(F) (sp->F.y - sp->F.h/2) /* top */
1402 # define T(F) (1-(sp->F.y + sp->F.h/2)) /* bottom */
1403 # define D(F,G) sqrt(F(from)*F(from) + G(to)*G(to)) /* corner traveled */
1408 double cx = sp->to.x - sp->from.x;
1409 double cy = sp->to.y - sp->from.y;
1410 double C = sqrt(cx*cx + cy*cy);
1411 double dist = MAX (BL, MAX (BR, MAX (TL, MAX (TR, C))));
1418 int steps = 1 + dist * 28;
1419 if (steps > 10) steps = 10;
1421 sp->duration = steps * 0.2 / speed;
1424 sp->duration += 1.5 / speed; /* For linger added by animate_sprite_path() */
1425 if (blink_p) sp->duration += 0.6 / speed;
1430 /* Convert the sprite to a jerky transition.
1431 Instead of smoothly animating, move in discrete steps,
1432 using multiple staggered sprites.
1435 animate_sprite_path (ModeInfo *mi, sprite *sp, Bool blink_p)
1438 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1439 double dx = sp->to.x - sp->from.x;
1440 double dy = sp->to.y - sp->from.y;
1441 double dw = sp->to.w - sp->from.w;
1442 double dh = sp->to.h - sp->from.h;
1443 double linger = 1.5 / speed;
1444 double blinger = 0.6 / speed;
1445 double dur = sp->duration - linger - (blink_p ? blinger : 0);
1446 int steps = dur / 0.3 * speed; /* step duration in seconds */
1449 if (sp->type == IMAGE)
1452 if (steps < 2) steps = 2;
1453 if (steps > 10) steps = 10;
1455 /* if (dur <= 0.01) abort(); */
1457 linger = blinger = 0;
1459 for (i = 0; i <= steps; i++)
1461 sprite *sp2 = copy_sprite (mi, sp);
1464 sp2->to.x = (sp->current.x + i * dx / steps);
1465 sp2->to.y = (sp->current.y + i * dy / steps);
1466 sp2->to.w = (sp->current.w + i * dw / steps);
1467 sp2->to.h = (sp->current.h + i * dh / steps);
1468 sp2->current = sp2->from = sp2->to;
1469 sp2->duration = dur / steps;
1470 sp2->pause_duration += i * sp2->duration;
1471 sp2->remain_p = False;
1472 sp2->fatbits_p = True;
1475 sp2->duration += linger; /* last one lingers for a bit */
1477 if (i == steps && !blink_p)
1479 sp2->remain_p = sp->remain_p;
1480 sp2->fatbits_p = False;
1483 if (sp2->type == IMAGE && i > 0)
1484 push_flash_sprite (mi, sp2);
1486 if (sp2->type == RETICLE || sp2->type == BOX)
1488 sp2 = push_text_sprite (mi, sp2);
1490 sp2->duration += linger * 2;
1494 if (blink_p && blinger) /* last one blinks before vanishing */
1497 for (i = 1; i <= blinkers; i++)
1499 sprite *sp2 = copy_sprite (mi, sp);
1502 sp2->current = sp2->from = sp->to;
1503 sp2->duration = blinger / blinkers;
1504 sp2->pause_duration += dur + linger + i * sp2->duration;
1505 sp2->remain_p = False;
1508 sp2->remain_p = sp->remain_p;
1509 sp2->fatbits_p = False;
1514 /* Fade out the template sprite. It might not have even appeared yet. */
1515 fadeout_sprite (mi, sp);
1520 /* Input rect is of a reticle or box.
1521 Output rect is what the image's rect should be so that the only part
1522 visible is the part indicated by the input rect.
1525 compute_image_rect (rect *r, sprite *img, Bool inverse_p)
1527 double scale = (inverse_p ? 1/r->w : r->w);
1528 double dx = r->x - 0.5;
1529 double dy = r->y - 0.5;
1531 /* Adjust size and center by zoom factor */
1532 r->w = img->current.w / scale;
1533 r->h = img->current.h / scale;
1534 r->x = 0.5 + (img->current.x - 0.5) / scale;
1535 r->y = 0.5 + (img->current.y - 0.5) / scale;
1541 dx = -dx; /* #### Close but not quite right */
1550 /* Sets 'to' such that the image zooms out so that the only part visible
1551 is the part indicated by the box.
1554 track_box_with_image (ModeInfo *mi, sprite *sp, sprite *img)
1556 rect r = sp->current;
1557 compute_image_rect (&r, img, sp->back_p);
1560 /* Never zoom out too far. */
1561 if (img->to.w < 1 && img->to.h < 1)
1563 if (img->to.w > img->to.h)
1565 img->to.w = img->to.w / img->to.h;
1570 img->to.h = img->to.h / img->to.w;
1575 /* Never pan beyond the bounds of the image. */
1576 if (img->to.x < -img->to.w/2+1) img->to.x = -img->to.w/2+1;
1577 if (img->to.x > img->to.w/2) img->to.x = img->to.w/2;
1578 if (img->to.y < -img->to.h/2+1) img->to.y = -img->to.h/2+1;
1579 if (img->to.y > img->to.h/2) img->to.y = img->to.h/2;
1584 tick_animation (ModeInfo *mi)
1586 esper_state *ss = &sss[MI_SCREEN(mi)];
1587 anim_state prev_state = ss->anim_state;
1591 switch (ss->anim_state) {
1593 ss->anim_state = GRID_ON;
1596 ss->anim_state = IMAGE_LOAD;
1599 /* Only advance once an image has loaded. */
1600 if (find_newest_sprite (mi, IMAGE))
1601 ss->anim_state = RETICLE_ON;
1603 ss->anim_state = IMAGE_LOAD;
1606 ss->anim_state = RETICLE_MOVE;
1610 ss->anim_state = BOX_MOVE;
1612 ss->anim_state = IMAGE_ZOOM;
1615 ss->anim_state = IMAGE_ZOOM;
1619 sprite *sp = find_newest_sprite (mi, IMAGE);
1621 ? MIN (sp->current.w, sp->current.h)
1624 ss->anim_state = IMAGE_UNLOAD;
1626 ss->anim_state = RETICLE_ON;
1629 case IMAGE_FORCE_UNLOAD:
1630 ss->anim_state = IMAGE_UNLOAD;
1633 ss->anim_state = IMAGE_LOAD;
1636 ss->anim_state = MANUAL_BOX;
1640 case MANUAL_RETICLE_ON:
1641 ss->anim_state = MANUAL_RETICLE;
1643 case MANUAL_RETICLE:
1650 ss->anim_start = ss->now;
1651 ss->anim_duration = 0;
1654 fprintf (stderr, "%s: entering %s\n",
1655 progname, state_name (ss->anim_state));
1657 switch (ss->anim_state) {
1659 case GRID_ON: /* Start the grid fading in. */
1660 if (! find_newest_sprite (mi, GRID))
1662 sp = new_sprite (mi, GRID);
1664 sp->fade_duration = 1.0 / speed;
1665 sp->duration = 2.0 / speed;
1666 sp->remain_p = True;
1667 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1673 fadeout_sprites (mi, IMAGE);
1674 sp = new_sprite (mi, IMAGE);
1677 if (debug_p) fprintf (stderr, "%s: image load failed\n", progname);
1681 sp->fade_duration = 0.5 / speed;
1682 sp->duration = sp->fade_duration * 3;
1683 sp->remain_p = True;
1684 /* If we zoom in, we lose the pulse at the end. */
1685 /* sp->from.w = sp->from.h = 0.0001; */
1686 sp->current = sp->from;
1688 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1691 sp = push_text_sprite (mi, sp);
1692 sp->fade_duration = 0.2 / speed;
1693 sp->pause_duration = 0;
1694 sp->duration = 2.5 / speed;
1697 case IMAGE_FORCE_UNLOAD:
1701 sp = find_newest_sprite (mi, IMAGE);
1703 sp->fade_duration = ((prev_state == IMAGE_FORCE_UNLOAD ? 0.2 : 3.0)
1705 fadeout_sprites (mi, IMAGE);
1706 fadeout_sprites (mi, RETICLE);
1707 fadeout_sprites (mi, BOX);
1708 fadeout_sprites (mi, TEXT);
1709 ss->anim_duration = (sp ? sp->fade_duration : 0) + 3.5 / speed;
1712 case RETICLE_ON: /* Display reticle at center. */
1713 fadeout_sprites (mi, TEXT);
1714 sp = new_sprite (mi, RETICLE);
1716 sp->fade_duration = 0.2 / speed;
1717 sp->pause_duration = 1.0 / speed;
1718 sp->duration = 1.5 / speed;
1719 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1721 ss->anim_duration -= sp->fade_duration * 2;
1725 /* Reticle has faded in. Now move it to somewhere else.
1726 Create N new reticle sprites, wih staggered pause_durations.
1731 GLfloat nx, ny, dist;
1733 do { /* pick a new position not too near the old */
1734 nx = 0.3 + BELLRAND(0.4);
1735 ny = 0.3 + BELLRAND(0.4);
1736 dist = sqrt ((nx-ox)*(nx-ox) + (ny-oy)*(ny-oy));
1737 } while (dist < 0.1);
1739 sp = new_sprite (mi, RETICLE);
1744 sp->current = sp->to = sp->from;
1747 sp->fade_duration = 0.2 / speed;
1748 sp->pause_duration = 0;
1749 compute_sprite_duration (mi, sp, False);
1751 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1752 sp->duration - 0.1);
1753 animate_sprite_path (mi, sp, False);
1758 /* Reticle has moved, and faded out.
1759 Start the box zooming into place.
1767 /* Find the last-added reticle, for our destination position. */
1769 for (i = 0; i < ss->nsprites; i++)
1771 sprite *sp2 = ss->sprites[i];
1772 if (sp2->type == RETICLE &&
1773 (!sp || sp->start_time < sp2->start_time))
1785 fprintf (stderr, "%s: no reticle before box?\n", progname);
1788 z = 0.3 + frand(0.5);
1790 /* Ensure that the selected box is contained within the screen */
1792 double margin = 0.005;
1793 double maxw = 2 * MIN (1 - margin - nx, nx - margin);
1794 double maxh = 2 * MIN (1 - margin - ny, ny - margin);
1795 double max = MIN (maxw, maxh);
1796 if (z > max) z = max;
1799 sp = new_sprite (mi, BOX);
1805 sp->current = sp->from;
1811 /* Maybe zoom out instead of in.
1814 sprite *img = find_newest_sprite (mi, IMAGE);
1815 double depth = MIN (img->current.w, img->current.h);
1816 if (depth > 1 && /* if zoomed in */
1817 (depth < 6 ? !(random() % 5) : /* 20% */
1818 depth < 12 ? !(random() % 2) : /* 50% */
1819 (random() % 3))) /* 66% */
1822 if (depth < 1.5 && z < 0.8)
1824 z = 0.8; /* don't zoom out much past 100% */
1831 sp->fade_duration = 0.2 / speed;
1832 sp->pause_duration = 2.0 / speed;
1833 compute_sprite_duration (mi, sp, True);
1834 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1835 sp->duration - 0.1);
1836 animate_sprite_path (mi, sp, True);
1842 /* Box has moved, and faded out.
1843 Or, if no box, then just a reticle.
1844 Zoom the underlying image to track the box's position. */
1848 /* Find latest box or reticle, for our destination position. */
1849 sp = find_newest_sprite (mi, BOX);
1851 sp = find_newest_sprite (mi, RETICLE);
1855 fprintf (stderr, "%s: no box or reticle before image\n",
1860 img = find_newest_sprite (mi, IMAGE);
1864 fprintf (stderr, "%s: no image?\n", progname);
1868 img2 = copy_sprite (mi, img);
1871 img2->from = img->current;
1873 fadeout_sprite (mi, img);
1875 track_box_with_image (mi, sp, img2);
1877 img2->fade_duration = 0.2 / speed;
1878 img2->pause_duration = 0.5 / speed;
1879 img2->remain_p = True;
1880 img2->throb_p = False;
1881 compute_sprite_duration (mi, img2, False);
1883 img->start_time += img2->pause_duration;
1885 ss->anim_duration = (img2->pause_duration + img2->fade_duration * 2 +
1887 animate_sprite_path (mi, img2, False);
1888 fadeout_sprites (mi, TEXT);
1893 case MANUAL_RETICLE_ON:
1897 case MANUAL_RETICLE:
1899 sprite_type tt = (ss->anim_state == MANUAL_BOX ? BOX : RETICLE);
1900 sprite *osp = find_newest_sprite (mi, tt);
1901 fadeout_sprites (mi, RETICLE);
1902 fadeout_sprites (mi, BOX);
1904 sp = new_sprite (mi, tt);
1907 sp->from = osp->current;
1915 sp->to = sp->current = sp->from;
1916 sp->fade_duration = 0.2 / speed;
1917 sp->duration = 0.2 / speed;
1918 sp->remain_p = True;
1919 sp->throb_p = False;
1920 ss->anim_duration = 9999;
1925 fprintf (stderr,"%s: unknown state %d\n",
1926 progname, (int) ss->anim_state);
1932 /* Copied from gltrackball.c, sigh.
1935 adjust_for_device_rotation (double *x, double *y, double *w, double *h)
1937 int rot = (int) current_device_rotation();
1940 while (rot <= -180) rot += 360;
1941 while (rot > 180) rot -= 360;
1943 if (rot > 135 || rot < -135) /* 180 */
1948 else if (rot > 45) /* 90 */
1950 swap = *x; *x = *y; *y = swap;
1951 swap = *w; *w = *h; *h = swap;
1954 else if (rot < -45) /* 270 */
1956 swap = *x; *x = *y; *y = swap;
1957 swap = *w; *w = *h; *h = swap;
1964 esper_handle_event (ModeInfo *mi, XEvent *event)
1966 esper_state *ss = &sss[MI_SCREEN(mi)];
1968 if (event->xany.type == Expose ||
1969 event->xany.type == GraphicsExpose ||
1970 event->xany.type == VisibilityNotify)
1974 else if (event->xany.type == KeyPress)
1979 double delta = 0.025;
1980 double margin = 0.005;
1983 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1987 ss->anim_state = IMAGE_FORCE_UNLOAD;
1991 if (! find_newest_sprite (mi, IMAGE))
1992 return False; /* Too early */
1994 ss->now = double_time();
1996 # define BONK() do { \
1997 if (ss->anim_state != MANUAL_BOX_ON && \
1998 ss->anim_state != MANUAL_BOX) { \
1999 ss->anim_state = MANUAL_BOX_ON; \
2000 tick_animation (mi); \
2002 sp = find_newest_sprite (mi, BOX); \
2003 if (!sp) abort() ; \
2004 sp->from = sp->current; \
2005 sp->to = sp->from; \
2006 sp->start_time = ss->now - sp->fade_duration; \
2010 if (keysym == XK_Left || c == ',' || c == '<')
2015 else if (keysym == XK_Right || c == '.' || c == '>')
2020 else if (keysym == XK_Down || c == '-')
2025 else if (keysym == XK_Up || c == '=')
2028 sp->to.y += delta, ok = True;
2030 else if (keysym == XK_Prior || c == '+')
2034 sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2036 else if (keysym == XK_Next || c == '_')
2040 sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2042 else if ((c == ' ' || c == '\t') && debug_p &&
2043 ss->anim_state == MANUAL_BOX)
2045 BONK(); /* Null motion: just flash the current image. */
2047 else if ((keysym == XK_Home || c == ' ' || c == '\t') &&
2048 ss->anim_state == MANUAL_BOX)
2056 else if ((c == '\r' || c == '\n' || c == 033) &&
2057 ss->anim_state == MANUAL_BOX)
2060 ss->anim_state = BOX_MOVE;
2061 ss->anim_duration = 9999;
2062 ss->anim_start = ss->now - ss->anim_duration;
2063 fadeout_sprite (mi, sp);
2072 /* Keep it on screen */
2073 if (sp->to.w > 1 - margin)
2075 GLfloat r = sp->to.h / sp->to.w;
2076 sp->to.w = 1-margin;
2077 sp->to.h = (1-margin) * r;
2081 GLfloat r = sp->to.h / sp->to.w;
2082 sp->to.w = (1-margin) / r;
2083 sp->to.h = 1-margin;
2086 if (sp->to.x - sp->to.w/2 < margin)
2087 sp->to.x = sp->to.w/2 + margin;
2088 if (sp->to.y - sp->to.h/2 < margin)
2089 sp->to.y = sp->to.h/2 + margin;
2091 if (sp->to.x + sp->to.w/2 >= 1 + margin)
2092 sp->to.x = 1 - (sp->to.w/2 + margin);
2093 if (sp->to.y + sp->to.h/2 >= 1 + margin)
2094 sp->to.y = 1 - (sp->to.h/2 + margin);
2096 /* Now let's give a momentary glimpse of what the image would do. */
2102 /* Find the lingering image */
2103 /* img = find__sprite (mi, IMAGE); */
2104 for (i = 0; i < ss->nsprites; i++)
2106 sprite *sp2 = ss->sprites[i];
2107 if (sp2->type == IMAGE &&
2110 (img->start_time < sp2->start_time &&
2111 ss->now >= sp2->start_time + sp2->pause_duration)))
2116 img = copy_sprite (mi, img);
2117 img->pause_duration = 0;
2118 img->fade_duration = 0.1 / speed;
2119 img->duration = 0.5 / speed;
2120 img->start_time = ss->now;
2121 img->remain_p = False;
2122 track_box_with_image (mi, sp, img);
2123 img->from = img->current = img->to;
2128 else if (event->xany.type == ButtonPress &&
2129 event->xbutton.button == Button1)
2131 ss->button_down_p = 1;
2134 else if (event->xany.type == ButtonRelease &&
2135 event->xbutton.button == Button1)
2137 ss->button_down_p = 0;
2139 if (ss->anim_state == MANUAL_BOX)
2141 sprite *sp = find_newest_sprite (mi, BOX);
2142 if (sp) fadeout_sprite (mi, sp);
2143 ss->anim_state = BOX_MOVE;
2144 ss->anim_duration = 9999;
2145 ss->anim_start = ss->now - ss->anim_duration;
2147 else if (ss->anim_state == MANUAL_RETICLE)
2149 sprite *sp = find_newest_sprite (mi, RETICLE);
2150 if (sp) fadeout_sprite (mi, sp);
2151 ss->anim_state = RETICLE_MOVE;
2152 ss->anim_duration = 9999;
2153 ss->anim_start = ss->now - ss->anim_duration;
2157 else if (event->xany.type == MotionNotify &&
2158 ss->button_down_p &&
2159 (ss->anim_state == MANUAL_RETICLE ||
2160 ss->anim_state == RETICLE_MOVE))
2163 double x = event->xmotion.x;
2164 double y = event->xmotion.y;
2165 double w = MI_WIDTH(mi);
2166 double h = MI_HEIGHT(mi);
2168 adjust_for_device_rotation (&x, &y, &w, &h);
2172 if (ss->anim_state != MANUAL_RETICLE_ON &&
2173 ss->anim_state != MANUAL_RETICLE)
2175 ss->anim_state = MANUAL_RETICLE_ON;
2176 tick_animation (mi);
2178 sp = find_newest_sprite (mi, RETICLE);
2180 sp->from = sp->current;
2182 sp->start_time = ss->now - sp->fade_duration;
2183 sp->remain_p = True;
2185 sp->current.x = MIN (0.95, MAX (0.05, x));
2186 sp->current.y = MIN (0.95, MAX (0.05, y));
2187 sp->from = sp->to = sp->current;
2189 /* Don't update the text sprite more often than once a second. */
2191 sprite *sp2 = find_newest_sprite (mi, TEXT);
2192 if (!sp2 || sp2->start_time < ss->now-1)
2194 fadeout_sprites (mi, TEXT);
2195 sp = push_text_sprite (mi, sp);
2196 sp->remain_p = True;
2202 else if (event->xany.type == MotionNotify &&
2203 ss->button_down_p &&
2204 (ss->anim_state == MANUAL_BOX ||
2205 ss->anim_state == BOX_MOVE))
2208 double x = event->xmotion.x;
2209 double y = event->xmotion.y;
2210 double w = MI_WIDTH(mi);
2211 double h = MI_HEIGHT(mi);
2215 adjust_for_device_rotation (&x, &y, &w, &h);
2220 max = (2 * (0.5 - MAX (fabs (sp->current.x - 0.5),
2221 fabs (sp->current.y - 0.5)))
2224 x = fabs (x - sp->current.x);
2225 y = fabs (y - sp->current.y);
2228 sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*x));
2230 sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*y));
2231 sp->from = sp->to = sp->current;
2233 /* Don't update the text sprite more often than once a second. */
2235 sprite *sp2 = find_newest_sprite (mi, TEXT);
2236 if (!sp2 || sp2->start_time < ss->now-1)
2238 fadeout_sprites (mi, TEXT);
2239 sp = push_text_sprite (mi, sp);
2240 sp->remain_p = True;
2246 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
2248 ss->anim_state = IMAGE_FORCE_UNLOAD;
2258 reshape_esper (ModeInfo *mi, int width, int height)
2262 glViewport (0, 0, width, height);
2263 glMatrixMode (GL_PROJECTION);
2265 glRotatef (current_device_rotation(), 0, 0, 1);
2266 glMatrixMode (GL_MODELVIEW);
2274 if (s < 0.1) s = 0.1;
2278 glTranslatef (-0.5, -0.5, 0);
2280 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2282 /* Stretch each existing image to match new window aspect. */
2284 esper_state *ss = &sss[MI_SCREEN(mi)];
2286 for (i = 0; i < ss->nsprites; i++)
2288 sprite *sp = ss->sprites[i];
2289 if (sp && sp->type == IMAGE && sp->img && sp->img->loaded_p)
2291 GLfloat sp_asp = sp->current.h / sp->current.w;
2292 GLfloat img_asp = (sp->img->geom.height /
2293 (GLfloat) sp->img->geom.width);
2294 GLfloat new_win = (MI_WIDTH(mi) / (double) MI_HEIGHT(mi));
2295 GLfloat old_win = sp_asp / img_asp;
2296 GLfloat r = old_win / new_win;
2316 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
2319 char *string = get_string_resource (mi->dpy, key, "EsperColor");
2320 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
2322 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
2327 color[0] = xcolor.red / 65536.0;
2328 color[1] = xcolor.green / 65536.0;
2329 color[2] = xcolor.blue / 65536.0;
2335 init_esper (ModeInfo *mi)
2337 int screen = MI_SCREEN(mi);
2339 int wire = MI_IS_WIREFRAME(mi);
2344 if ((ss->glx_context = init_GL(mi)) != NULL) {
2345 reshape_esper (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2350 parse_color (mi, "gridColor", ss->grid_color);
2351 parse_color (mi, "reticleColor", ss->reticle_color);
2352 parse_color (mi, "textColor", ss->text_color);
2354 glDisable (GL_LIGHTING);
2355 glDisable (GL_DEPTH_TEST);
2356 glDepthMask (GL_FALSE);
2357 glEnable (GL_CULL_FACE);
2358 glCullFace (GL_BACK);
2362 glEnable (GL_TEXTURE_2D);
2363 glShadeModel (GL_SMOOTH);
2364 glEnable (GL_BLEND);
2365 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2368 ss->font_data = load_texture_font (mi->dpy, "titleFont");
2370 ss->now = double_time();
2371 ss->dawn_of_time = ss->now;
2375 ss->anim_state = BLANK;
2377 ss->anim_duration = 0;
2382 draw_esper (ModeInfo *mi)
2384 esper_state *ss = &sss[MI_SCREEN(mi)];
2386 if (!ss->glx_context)
2389 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
2391 mi->polygon_count = 0;
2393 ss->now = double_time();
2397 if (ss->now >= ss->anim_start + ss->anim_duration)
2398 tick_animation (mi);
2400 if (mi->fps_p) do_fps (mi);
2403 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
2406 XSCREENSAVER_MODULE ("Esper", esper)