1 /* esper, Copyright (c) 2017-2018 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"
131 #elif 0 /* real X11, XQueryFont() */
132 # define TITLE_FONT "-*-courier-bold-r-*-*-*-100-*-*-m-*-*-*"
133 #else /* real X11, load_font_retry() */
134 # define TITLE_FONT "-*-ocr a std-medium-r-*-*-*-100-*-*-m-*-*-*"
137 #define DEFAULTS "*delay: 20000 \n" \
138 "*wireframe: False \n" \
139 "*showFPS: False \n" \
142 "*titleFont: " TITLE_FONT "\n" \
143 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
144 "*grabDesktopImages: False \n" \
145 "*chooseRandomImages: True \n" \
146 "*gridColor: #4444FF\n" \
147 "*reticleColor: #FFFF77\n" \
148 "*textColor: #FFFFBB\n" \
150 # define free_esper 0
151 # define refresh_esper 0
152 # define release_esper 0
153 # include "xlockmore.h"
156 #define countof(x) (sizeof((x))/sizeof((*x)))
159 #define RANDSIGN() ((random() & 1) ? 1 : -1)
161 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
167 # define DEF_GRID_SIZE "11"
168 # define DEF_GRID_THICKNESS "15"
169 # define DEF_TITLES "True"
170 # define DEF_SPEED "1.0"
171 # define DEF_DEBUG "False"
173 #include "grab-ximage.h"
176 #ifdef HAVE_XSHM_EXTENSION
177 # include "xshm.h" /* to get <sys/shm.h> */
187 unsigned long id; /* unique */
188 char *title; /* the filename of this image */
189 int w, h; /* size in pixels of the image */
190 int tw, th; /* size in pixels of the texture */
191 XRectangle geom; /* where in the image the bits are */
192 Bool loaded_p; /* whether the image has finished loading */
193 Bool used_p; /* whether the image has yet appeared
195 GLuint texid; /* which texture contains the image */
196 int refcount; /* how many sprites refer to this image */
217 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
218 typedef enum { IMAGE, RETICLE, BOX, GRID, FLASH, TEXT } sprite_type;
221 unsigned long id; /* unique */
223 image *img; /* type = IMAGE */
224 unsigned long text_id; /* type = TEXT */
227 GLfloat thickness_scale; /* line and image types */
229 double start_time; /* when this animation began */
230 double duration; /* lifetime of sprite in seconds; 0 = inf */
231 double fade_duration; /* speed of fade in and fade out */
232 double pause_duration; /* delay before fade-in starts */
233 Bool remain_p; /* pause forever before fade-out */
234 rect from, to, current; /* the journey this image is taking */
235 sprite_state state; /* the state we're in right now */
236 double state_time; /* time of last state change */
237 int frame_count; /* frames since last state change */
238 Bool fatbits_p; /* For image texture rendering */
239 Bool back_p; /* If BOX, zooming out, not in */
244 GLXContext *glx_context;
245 int nimages; /* how many images are loaded or loading now */
246 image *images[10]; /* pointers to the images */
248 int nsprites; /* how many sprites are animating right now */
249 sprite *sprites[100]; /* pointers to the live sprites */
251 double now; /* current time in seconds */
252 double dawn_of_time; /* when the program launched */
253 double image_load_time; /* time when we last loaded a new image */
255 texture_font_data *font_data;
257 int sprite_id, image_id; /* debugging id counters */
259 GLfloat grid_color[4], reticle_color[4], text_color[4];
261 anim_state anim_state; /* Counters for global animation state, */
262 double anim_start, anim_duration;
268 static esper_state *sss = NULL;
271 /* Command-line arguments
273 static int grid_size;
274 static int grid_thickness;
276 static Bool do_titles; /* Display image titles. */
277 static GLfloat speed;
278 static Bool debug_p; /* Be loud and do weird things. */
281 static XrmOptionDescRec opts[] = {
282 { "-speed", ".speed", XrmoptionSepArg, 0 },
283 { "-titles", ".titles", XrmoptionNoArg, "True" },
284 { "-no-titles", ".titles", XrmoptionNoArg, "False" },
285 { "-debug", ".debug", XrmoptionNoArg, "True" },
288 static argtype vars[] = {
289 { &grid_size, "gridSize", "GridSize", DEF_GRID_SIZE, t_Int},
290 { &grid_thickness,"gridThickness","GridThickness",DEF_GRID_THICKNESS, t_Int},
291 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
292 { &speed, "speed", "Speed", DEF_SPEED, t_Float},
293 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
296 ENTRYPOINT ModeSpecOpt esper_opts = {countof(opts), opts, countof(vars), vars, NULL};
299 /* Returns the current time in seconds as a double.
305 # ifdef GETTIMEOFDAY_TWO_ARGS
307 gettimeofday(&now, &tzp);
312 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
316 state_name (anim_state s)
319 case BLANK: return "BLANK";
320 case GRID_ON: return "GRID_ON";
321 case IMAGE_LOAD: return "IMAGE_LOAD";
322 case IMAGE_UNLOAD: return "IMAGE_UNLOAD";
323 case IMAGE_FORCE_UNLOAD: return "IMAGE_FORCE_UNLOAD";
324 case REPOSITION: return "REPOSITION";
325 case RETICLE_ON: return "RETICLE_ON";
326 case RETICLE_MOVE: return "RETICLE_MOVE";
327 case BOX_MOVE: return "BOX_MOVE";
328 case IMAGE_ZOOM: return "IMAGE_ZOOM";
329 case MANUAL_BOX_ON: return "MANUAL_BOX_ON";
330 case MANUAL_BOX: return "MANUAL_BOX";
331 case MANUAL_RETICLE_ON: return "MANUAL_RETICLE_ON";
332 case MANUAL_RETICLE: return "MANUAL_RETICLE";
333 default: return "UNKNOWN";
338 static void image_loaded_cb (const char *filename, XRectangle *geom,
339 int image_width, int image_height,
340 int texture_width, int texture_height,
344 /* Allocate an image structure and start a file loading in the background.
347 alloc_image (ModeInfo *mi)
349 esper_state *ss = &sss[MI_SCREEN(mi)];
350 int wire = MI_IS_WIREFRAME(mi);
351 image *img = (image *) calloc (1, sizeof (*img));
353 img->id = ++ss->image_id;
354 img->loaded_p = False;
358 glGenTextures (1, &img->texid);
359 if (img->texid <= 0) abort();
361 ss->image_load_time = ss->now;
364 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
367 /* If possible, load images at much higher resolution than the window,
368 to facilitate deep zooms.
370 int max_max = 4096; /* ~12 megapixels */
373 # if defined(HAVE_XSHM_EXTENSION) && \
374 !defined(HAVE_MOBILE) && \
377 /* Try not to ask for an image larger than the SHM segment size.
378 If XSHM fails in a real-X11 world, it can take a staggeringly long
379 time to transfer the image bits from the server over Xproto -- like,
380 *18 seconds* for 4096 px and 8 seconds for 3072 px on MacOS XQuartz.
381 What madness is this?
383 unsigned long shmmax = 0;
386 /* Linux 2.6 defines this to be 0x2000000, but on CentOS 6.9,
387 "sysctl kernel.shmmax" reports a luxurious 0x1000000000. */
389 # elif defined(__APPLE__)
390 /* MacOS 10.13 "sysctl kern.sysv.shmmax" is paltry: */
392 # endif /* !SHMMAX */
396 /* Roughly, bytes => NxN. b = (n/8)*4n = n*n*4, so n^2 = 2b, so: */
397 unsigned long n = sqrt(shmmax)/2;
401 # endif /* HAVE_XSHM_EXTENSION and real X11 */
403 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
404 if (max > max_max) max = max_max;
406 /* Never ask for an image smaller than the window, even if that
407 will make XSHM fall back to Xproto. */
408 if (max < MI_WIDTH(mi) || max < MI_HEIGHT(mi))
411 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
412 max, max, False, img->texid, image_loaded_cb, img);
415 ss->images[ss->nimages++] = img;
416 if (ss->nimages >= countof(ss->images)) abort();
422 /* Callback that tells us that the texture has been loaded.
425 image_loaded_cb (const char *filename, XRectangle *geom,
426 int image_width, int image_height,
427 int texture_width, int texture_height,
430 image *img = (image *) closure;
431 ModeInfo *mi = img->mi;
433 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
435 int wire = MI_IS_WIREFRAME(mi);
439 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
440 img->h = MI_HEIGHT (mi);
441 img->geom.width = img->w;
442 img->geom.height = img->h;
446 if (image_width == 0 || image_height == 0)
449 img->w = image_width;
450 img->h = image_height;
451 img->tw = texture_width;
452 img->th = texture_height;
454 img->title = (filename ? strdup (filename) : 0);
456 ow = img->geom.width;
457 oh = img->geom.height;
459 /* If the image's width doesn't come back as the width of the screen,
460 then the image must have been scaled down (due to insufficient
461 texture memory.) Scale up the coordinates to stretch the image
464 if (img->w != MI_WIDTH(mi))
466 double scale = (double) MI_WIDTH(mi) / img->w;
471 img->geom.x *= scale;
472 img->geom.y *= scale;
473 img->geom.width *= scale;
474 img->geom.height *= scale;
477 /* xscreensaver-getimage returns paths relative to the image directory
478 now, so leave the sub-directory part in. Unless it's an absolute path.
480 if (img->title && img->title[0] == '/')
482 /* strip filename to part between last "/" and last ".". */
483 char *s = strrchr (img->title, '/');
484 if (s) strcpy (img->title, s+1);
485 s = strrchr (img->title, '.');
489 # if !(__APPLE__ && TARGET_IPHONE_SIMULATOR || !defined(__OPTIMIZE__))
492 fprintf (stderr, "%s: loaded %lu \"%s\" %dx%d\n",
493 progname, img->id, (img->title ? img->title : "(null)"),
497 img->loaded_p = True;
502 /* Free the image and texture, after nobody is referencing it.
505 destroy_image (ModeInfo *mi, image *img)
507 esper_state *ss = &sss[MI_SCREEN(mi)];
508 Bool freed_p = False;
512 if (!img->loaded_p) abort();
513 if (!img->used_p) abort();
514 if (img->texid <= 0) abort();
515 if (img->refcount != 0) abort();
517 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
518 if (ss->images[i] == img)
521 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
522 ss->images[j] = ss->images[j+1];
529 if (!freed_p) abort();
532 fprintf (stderr, "%s: unloaded img %2lu: \"%s\"\n",
533 progname, img->id, (img->title ? img->title : "(null)"));
535 if (img->title) free (img->title);
536 glDeleteTextures (1, &img->texid);
541 /* Return an image to use for a sprite.
542 If it's time for a new one, get a new one.
543 Otherwise, use an old one.
544 Might return 0 if the machine is really slow.
547 get_image (ModeInfo *mi)
549 esper_state *ss = &sss[MI_SCREEN(mi)];
551 image *loading_img = 0;
554 for (i = 0; i < ss->nimages; i++)
556 image *img2 = ss->images[i];
564 /* Make sure that there is always one unused image in the pipe.
566 if (!img && !loading_img)
573 /* Allocate a new sprite and start its animation going.
576 new_sprite (ModeInfo *mi, sprite_type type)
578 esper_state *ss = &sss[MI_SCREEN(mi)];
579 image *img = (type == IMAGE ? get_image (mi) : 0);
582 if (type == IMAGE && !img)
584 /* Oops, no images yet! The machine is probably hurting bad.
585 Let's give it some time before thrashing again. */
590 sp = (sprite *) calloc (1, sizeof (*sp));
591 sp->id = ++ss->sprite_id;
593 sp->start_time = ss->now;
594 sp->state_time = sp->start_time;
595 sp->thickness_scale = 1;
606 sp->img->used_p = True;
607 sp->duration = 0; /* forever, until further notice */
608 sp->fade_duration = 0.5;
610 /* Scale the sprite so that the image bits fill the window. */
612 double w = MI_WIDTH(mi);
613 double h = MI_HEIGHT(mi);
615 r = ((img->geom.height / (double) img->geom.width) * (w / h));
622 /* Pan to a random spot */
624 sp->to.y += frand ((sp->to.h - 1) / 2) * RANDSIGN();
626 sp->to.x += frand ((sp->to.w - 1) / 2) * RANDSIGN();
629 sp->from = sp->current = sp->to;
631 ss->sprites[ss->nsprites++] = sp;
632 if (ss->nsprites >= countof(ss->sprites)) abort();
639 copy_sprite (ModeInfo *mi, sprite *old)
641 sprite *sp = new_sprite (mi, (sprite_type) ~0L);
643 double tt = sp->start_time;
646 memcpy (sp, old, sizeof(*sp));
649 sp->state_time = sp->start_time = tt;
656 /* Free the given sprite, and decrement the reference count on its image.
659 destroy_sprite (ModeInfo *mi, sprite *sp)
661 esper_state *ss = &sss[MI_SCREEN(mi)];
662 Bool freed_p = False;
667 if (sp->state != DEAD) abort();
670 if (sp->type != IMAGE)
677 if (!img->loaded_p) abort();
678 if (!img->used_p) abort();
679 if (img->refcount <= 0) abort();
682 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
683 if (ss->sprites[i] == sp)
686 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
687 ss->sprites[j] = ss->sprites[j+1];
694 if (!freed_p) abort();
695 if (sp->text) free (sp->text);
702 if (img->refcount < 0) abort();
703 if (img->refcount == 0)
704 destroy_image (mi, img);
709 /* Updates the sprite for the current frame of the animation based on
710 its creation time compared to the current wall clock.
713 tick_sprite (ModeInfo *mi, sprite *sp)
715 esper_state *ss = &sss[MI_SCREEN(mi)];
716 image *img = sp->img;
717 double now = ss->now;
720 GLfloat visible = sp->duration + sp->fade_duration * 2;
721 GLfloat total = sp->pause_duration + visible;
723 if (sp->type != IMAGE)
725 if (sp->img) abort();
729 if (! sp->img) abort();
730 if (! img->loaded_p) abort();
733 /* pause fade duration fade
734 |------------|------------|---------|-----------|
735 ....----====##########====----....
739 secs = now - sp->start_time;
740 ratio = (visible <= 0 ? 1 : ((secs - sp->pause_duration) / visible));
741 if (ratio < 0) ratio = 0;
742 else if (ratio > 1) ratio = 1;
744 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
745 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
746 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
747 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
749 sp->thickness_scale = 1;
751 if (secs < sp->pause_duration)
756 else if (secs < sp->pause_duration + sp->fade_duration)
759 sp->opacity = (secs - sp->pause_duration) / (GLfloat) sp->fade_duration;
761 else if (sp->duration == 0 || /* 0 means infinite lifetime */
763 secs < sp->pause_duration + sp->fade_duration + sp->duration)
768 /* Just after reaching full opacity, pulse the width up and down. */
769 if (sp->fade_duration > 0 &&
770 secs < sp->pause_duration + sp->fade_duration * 2)
772 GLfloat f = ((secs - (sp->pause_duration + sp->fade_duration)) /
775 sp->thickness_scale = 1 + 3 * (f > 0.5 ? 1-f : f);
778 else if (secs < total)
781 sp->opacity = (total - secs) / sp->fade_duration;
793 /* Draw the given sprite at the phase of its animation dictated by
794 its creation time compared to the current wall clock.
797 draw_image_sprite (ModeInfo *mi, sprite *sp)
799 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
800 int wire = MI_IS_WIREFRAME(mi);
801 image *img = sp->img;
803 if (! sp->img) abort();
804 if (! img->loaded_p) abort();
808 GLfloat s = 1 + (sp->thickness_scale - 1) / 40.0;
809 glTranslatef (0.5, 0.5, 0);
811 glTranslatef (-0.5, -0.5, 0);
813 glTranslatef (sp->current.x, sp->current.y, 0);
814 glScalef (sp->current.w, sp->current.h, 1);
816 glTranslatef (-0.5, -0.5, 0);
818 if (wire) /* Draw a grid inside the box */
821 GLfloat dx = dy * img->w / img->h;
825 glColor4f (sp->opacity, 0, 0, 1);
827 glColor4f (0, 0, sp->opacity, 1);
830 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
831 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
833 for (y = 0; y < 1+dy; y += dy)
835 GLfloat yy = (y > 1 ? 1 : y);
836 for (x = 0.5; x < 1+dx; x += dx)
838 GLfloat xx = (x > 1 ? 1 : x);
839 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
840 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
842 for (x = 0.5; x > -dx; x -= dx)
844 GLfloat xx = (x < 0 ? 0 : x);
845 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
846 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
851 else /* Draw the texture quad */
853 GLfloat texw = img->geom.width / (GLfloat) img->tw;
854 GLfloat texh = img->geom.height / (GLfloat) img->th;
855 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
856 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
857 GLfloat texx2 = texx1 + texw;
858 GLfloat texy2 = texy1 + texh;
859 GLfloat o = sp->opacity;
860 GLint mag = (sp->fatbits_p ? GL_NEAREST : GL_LINEAR);
862 glBindTexture (GL_TEXTURE_2D, img->texid);
864 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
865 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
866 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
867 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mag);
869 /* o = 1 - sin ((1 - o*o*o) * M_PI/2); */
870 glColor4f (1, 1, 1, o);
872 glNormal3f (0, 0, 1);
874 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
875 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
876 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
877 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
880 if (debug_p) /* Draw a border around the image */
882 if (!wire) glDisable (GL_TEXTURE_2D);
883 glColor4f (sp->opacity, 0, 0, 1);
884 glBegin (GL_LINE_LOOP);
885 glVertex3f (0, 0, 0);
886 glVertex3f (0, 1, 0);
887 glVertex3f (1, 1, 0);
888 glVertex3f (1, 0, 0);
890 if (!wire) glEnable (GL_TEXTURE_2D);
899 draw_line_sprite (ModeInfo *mi, sprite *sp)
901 esper_state *ss = &sss[MI_SCREEN(mi)];
902 int wire = MI_IS_WIREFRAME(mi);
903 int w = MI_WIDTH(mi);
904 int h = MI_HEIGHT(mi);
905 int wh = (w > h ? w : h);
906 int gs = (sp->type == RETICLE ? grid_size+1 : grid_size);
907 int sx = wh / (gs + 1);
910 GLfloat t = grid_thickness * sp->thickness_scale;
914 GLfloat x = w * sp->current.x;
915 GLfloat y = h * sp->current.y;
916 GLfloat bw = w * sp->current.w;
917 GLfloat bh = h * sp->current.h;
919 if (MI_WIDTH(mi) > 2560) t *= 3; /* Retina displays */
921 if (sx < 10) sx = 10;
924 if (t > sx/3) t = sx/3;
927 if (fade < 1) fade = 1;
929 if (t <= 0 || sp->opacity <= 0) return;
940 glOrtho (0, w, 0, h, -1, 1);
943 case GRID: memcpy (color, ss->grid_color, sizeof(color)); break;
944 case RETICLE: memcpy (color, ss->reticle_color, sizeof(color)); break;
945 case BOX: memcpy (color, ss->reticle_color, sizeof(color)); break;
949 if (sp->type == GRID)
951 GLfloat s = 1 + (sp->thickness_scale - 1) / 120.0;
952 glTranslatef (w/2, h/2, 0);
954 glTranslatef (-w/2, -h/2, 0);
959 if (!wire) glDisable (GL_TEXTURE_2D);
961 for (k = 0; k < fade; k++)
963 GLfloat t2 = t * (1 - (k / (fade * 1.0)));
965 color[3] = sp->opacity / fade;
968 glBegin (wire ? GL_LINES : GL_QUADS);
973 GLfloat xoff = (w - sx * (w / sx)) / 2.0;
974 GLfloat yoff = (h - sy * (h / sy)) / 2.0;
975 for (y = -sy/2+t2/2; y < h; y += sy)
976 for (x = -sx/2-t2/2; x < w; x += sx)
978 glVertex3f (xoff+x+t2, yoff+y, 0);
979 glVertex3f (xoff+x+t2, yoff+y+sy-t2, 0);
980 glVertex3f (xoff+x, yoff+y+sy-t2, 0);
981 glVertex3f (xoff+x, yoff+y, 0);
984 glVertex3f (xoff+x, yoff+y-t2, 0);
985 glVertex3f (xoff+x+sx, yoff+y-t2, 0);
986 glVertex3f (xoff+x+sx, yoff+y, 0);
987 glVertex3f (xoff+x, yoff+y, 0);
994 glVertex3f (x-bw/2-t2/2, y-bh/2-t2/2, 0);
995 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);
1000 glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
1001 glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
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 mi->polygon_count++;
1006 glVertex3f (x-bw/2+t2/2, y-bh/2+t2/2, 0);
1007 glVertex3f (x-bw/2+t2/2, y+bh/2-t2/2, 0);
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 mi->polygon_count++;
1012 glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
1013 glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
1014 glVertex3f (x+bw/2-t2/2, y+bh/2-t2/2, 0);
1015 glVertex3f (x+bw/2-t2/2, y-bh/2+t2/2, 0);
1016 mi->polygon_count++;
1020 glVertex3f (x+t2/2, y+sy/2-t2/2, 0);
1021 glVertex3f (x+t2/2, h, 0);
1022 glVertex3f (x-t2/2, h, 0);
1023 glVertex3f (x-t2/2, y+sy/2-t2/2, 0);
1024 mi->polygon_count++;
1026 glVertex3f (x-t2/2, y-sy/2+t2/2, 0);
1027 glVertex3f (x-t2/2, 0, 0);
1028 glVertex3f (x+t2/2, 0, 0);
1029 glVertex3f (x+t2/2, y-sy/2+t2/2, 0);
1030 mi->polygon_count++;
1032 glVertex3f (x-sx/2+t2/2, y+t2/2, 0);
1033 glVertex3f (0, y+t2/2, 0);
1034 glVertex3f (0, y-t2/2, 0);
1035 glVertex3f (x-sx/2+t2/2, y-t2/2, 0);
1036 mi->polygon_count++;
1038 glVertex3f (x+sx/2-t2/2, y-t2/2, 0);
1039 glVertex3f (w, y-t2/2, 0);
1040 glVertex3f (w, y+t2/2, 0);
1041 glVertex3f (x+sx/2-t2/2, y+t2/2, 0);
1042 mi->polygon_count++;
1052 if (!wire) glEnable (GL_TEXTURE_2D);
1056 static sprite * find_newest_sprite (ModeInfo *, sprite_type);
1057 static void compute_image_rect (rect *, sprite *, Bool);
1060 draw_text_sprite (ModeInfo *mi, sprite *sp)
1062 esper_state *ss = &sss[MI_SCREEN(mi)];
1063 int wire = MI_IS_WIREFRAME(mi);
1064 GLfloat w = MI_WIDTH(mi);
1065 GLfloat h = MI_HEIGHT(mi);
1074 if (sp->opacity <= 0)
1077 for (i = 0; i < ss->nsprites; i++)
1079 sprite *sp2 = ss->sprites[i];
1080 if (sp2->id == sp->text_id && sp2->state != DEAD)
1092 if (target->opacity <= 0 &&
1093 (target->state == NEW || target->state == IN))
1096 r = target->current;
1098 img = find_newest_sprite (mi, IMAGE);
1100 compute_image_rect (&r, img, target->back_p);
1102 mi->recursion_depth = (img
1103 ? MIN (img->current.w, img->current.h)
1106 x = abs ((int) (r.x * 10000)) % 10000;
1107 y = abs ((int) (r.y * 10000)) % 10000;
1108 z = abs ((int) (r.w * 10000)) % 10000;
1110 sprintf (text, "ZM %04d NS %04d EW %04d", z, y, x);
1112 if ((x == 0 || x == 5000) && /* startup */
1113 (y == 0 || y == 5000) &&
1114 (z == 0 || z == 5000))
1118 target->type == IMAGE &&
1119 target->remain_p) /* The initial background image */
1121 char *s = (target->img &&
1122 target->img->title && *target->img->title
1123 ? target->img->title
1126 int i = (L > 23 ? L-23 : 0);
1127 sprintf (text, ">>%-23s", target->img->title + i);
1128 for (s = text; *s; s++)
1129 if (*s >= 'a' && *s <= 'z') *s += ('A'-'a');
1130 else if (*s == '/' || *s == '-' || *s == '.') *s = '_';
1135 if (sp->text) free (sp->text);
1136 sp->text = strdup (text);
1138 else if (sp->text && *sp->text)
1139 /* The target sprite might be dead, but we saved our last text. */
1140 strcpy (text, sp->text);
1142 /* No target, no saved text. */
1145 texture_string_metrics (ss->font_data, text, &e, 0, 0);
1149 glOrtho (0, 1, 0, 1, -1, 1);
1151 /* Scale the text to fit N characters horizontally. */
1155 # else /* desktop */
1156 GLfloat c = (MI_WIDTH(mi) <= 640 ? 25 :
1157 MI_WIDTH(mi) <= 1280 ? 32 : 64);
1159 s = w / (e.ascent * c);
1163 x = (w - e.width) / 2;
1164 y = e.ascent + e.descent * 2;
1166 glScalef (1.0/w, 1.0/h, 1);
1167 glTranslatef (x, y, 0);
1169 memcpy (color, ss->text_color, sizeof(color));
1170 color[3] = sp->opacity;
1174 glEnable (GL_TEXTURE_2D);
1176 print_texture_string (ss->font_data, text);
1177 mi->polygon_count++;
1180 glDisable (GL_TEXTURE_2D);
1186 draw_flash_sprite (ModeInfo *mi, sprite *sp)
1188 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1189 GLfloat o = sp->opacity;
1192 o = 0.7; /* Too fast to see, so keep it consistent */
1195 int wire = MI_IS_WIREFRAME(mi);
1197 glDisable (GL_TEXTURE_2D);
1198 glColor4f (0, 0, 1, o);
1199 glColorMask (0, 0, 1, 1); /* write only into blue and alpha channels */
1201 glVertex3f (0, 0, 0);
1202 glVertex3f (1, 0, 0);
1203 glVertex3f (1, 1, 0);
1204 glVertex3f (0, 1, 0);
1206 glColorMask (1, 1, 1, 1);
1208 glEnable (GL_TEXTURE_2D);
1214 draw_sprite (ModeInfo *mi, sprite *sp)
1218 draw_image_sprite (mi, sp);
1223 draw_line_sprite (mi, sp);
1226 draw_text_sprite (mi, sp);
1229 draw_flash_sprite (mi, sp);
1238 tick_sprites (ModeInfo *mi)
1240 esper_state *ss = &sss[MI_SCREEN(mi)];
1242 for (i = 0; i < ss->nsprites; i++)
1243 tick_sprite (mi, ss->sprites[i]);
1245 for (i = 0; i < ss->nsprites; i++)
1247 sprite *sp = ss->sprites[i];
1248 if (sp->state == DEAD)
1250 destroy_sprite (mi, sp);
1258 draw_sprites (ModeInfo *mi)
1260 esper_state *ss = &sss[MI_SCREEN(mi)];
1263 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1269 GLfloat rot = current_device_rotation();
1270 glTranslatef (0.5, 0.5, 0);
1271 glRotatef(rot, 0, 0, 1);
1272 if ((rot > 45 && rot < 135) ||
1273 (rot < -45 && rot > -135))
1275 GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
1276 glScalef (s, 1/s, 1);
1278 glTranslatef (-0.5, -0.5, 0);
1282 /* Draw the images first, then the overlays. */
1283 for (i = 0; i < ss->nsprites; i++)
1284 if (ss->sprites[i]->type == IMAGE)
1285 draw_sprite (mi, ss->sprites[i]);
1286 for (i = 0; i < ss->nsprites; i++)
1287 if (ss->sprites[i]->type != IMAGE)
1288 draw_sprite (mi, ss->sprites[i]);
1292 if (debug_p) /* draw a white box (the "screen") */
1294 int wire = MI_IS_WIREFRAME(mi);
1296 if (!wire) glDisable (GL_TEXTURE_2D);
1298 glColor4f (1, 1, 1, 1);
1299 glBegin (GL_LINE_LOOP);
1300 glVertex3f (0, 0, 0);
1301 glVertex3f (0, 1, 0);
1302 glVertex3f (1, 1, 0);
1303 glVertex3f (1, 0, 0);
1306 if (!wire) glEnable (GL_TEXTURE_2D);
1312 fadeout_sprite (ModeInfo *mi, sprite *sp)
1314 esper_state *ss = &sss[MI_SCREEN(mi)];
1316 /* If it hasn't faded in yet, don't fade out. */
1317 if (ss->now <= sp->start_time + sp->pause_duration)
1318 sp->fade_duration = 0;
1320 /* Pretend it's at the point where it should fade out. */
1321 sp->pause_duration = 0;
1322 sp->duration = 9999;
1323 sp->remain_p = False;
1324 sp->start_time = ss->now - sp->duration;
1328 fadeout_sprites (ModeInfo *mi, sprite_type type)
1330 esper_state *ss = &sss[MI_SCREEN(mi)];
1332 for (i = 0; i < ss->nsprites; i++)
1334 sprite *sp = ss->sprites[i];
1335 if (sp->type == type)
1336 fadeout_sprite (mi, sp);
1342 find_newest_sprite (ModeInfo *mi, sprite_type type)
1344 esper_state *ss = &sss[MI_SCREEN(mi)];
1347 for (i = 0; i < ss->nsprites; i++)
1349 sprite *sp2 = ss->sprites[i];
1350 if (sp2->type == type &&
1352 (sp->start_time < sp2->start_time &&
1353 ss->now >= sp2->start_time + sp2->pause_duration)))
1360 /* Enqueue a text sprite describing the given sprite that runs at the
1364 push_text_sprite (ModeInfo *mi, sprite *sp)
1366 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1367 sprite *sp2 = new_sprite (mi, TEXT);
1369 sp2->text_id = sp->id;
1370 sp2->fade_duration = sp->fade_duration;
1371 sp2->duration = sp->duration;
1372 sp2->pause_duration = sp->pause_duration;
1377 /* Enqueue a flash sprite that fires at the same time.
1381 push_flash_sprite (ModeInfo *mi, sprite *sp)
1383 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1384 sprite *sp2 = new_sprite (mi, FLASH);
1386 if (sp->type != IMAGE) abort();
1387 sp2->text_id = sp->id;
1388 sp2->duration = MAX (0.07 / speed, 0.07);
1389 sp2->fade_duration = 0; /* Fading these is too fast to see */
1390 sp2->pause_duration = sp->pause_duration + (sp->fade_duration * 0.3);
1393 #endif /* !SMOOTH */
1396 /* Set the sprite's duration based on distance travelled.
1399 compute_sprite_duration (ModeInfo *mi, sprite *sp, Bool blink_p)
1401 /* Compute max distance traveled by any point (corners or center). */
1402 /* (cpp is the devil) */
1403 # define L(F) (sp->F.x - sp->F.w/2) /* delta of left edge, from/to */
1404 # define R(F) (1-(sp->F.x + sp->F.w/2)) /* right */
1405 # define B(F) (sp->F.y - sp->F.h/2) /* top */
1406 # define T(F) (1-(sp->F.y + sp->F.h/2)) /* bottom */
1407 # define D(F,G) sqrt(F(from)*F(from) + G(to)*G(to)) /* corner traveled */
1412 double cx = sp->to.x - sp->from.x;
1413 double cy = sp->to.y - sp->from.y;
1414 double C = sqrt(cx*cx + cy*cy);
1415 double dist = MAX (BL, MAX (BR, MAX (TL, MAX (TR, C))));
1422 int steps = 1 + dist * 28;
1423 if (steps > 10) steps = 10;
1425 sp->duration = steps * 0.2 / speed;
1428 sp->duration += 1.5 / speed; /* For linger added by animate_sprite_path() */
1429 if (blink_p) sp->duration += 0.6 / speed;
1434 /* Convert the sprite to a jerky transition.
1435 Instead of smoothly animating, move in discrete steps,
1436 using multiple staggered sprites.
1439 animate_sprite_path (ModeInfo *mi, sprite *sp, Bool blink_p)
1442 /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
1443 double dx = sp->to.x - sp->from.x;
1444 double dy = sp->to.y - sp->from.y;
1445 double dw = sp->to.w - sp->from.w;
1446 double dh = sp->to.h - sp->from.h;
1447 double linger = 1.5 / speed;
1448 double blinger = 0.6 / speed;
1449 double dur = sp->duration - linger - (blink_p ? blinger : 0);
1450 int steps = dur / 0.3 * speed; /* step duration in seconds */
1453 if (sp->type == IMAGE)
1456 if (steps < 2) steps = 2;
1457 if (steps > 10) steps = 10;
1459 /* if (dur <= 0.01) abort(); */
1461 linger = blinger = 0;
1463 for (i = 0; i <= steps; i++)
1465 sprite *sp2 = copy_sprite (mi, sp);
1468 sp2->to.x = (sp->current.x + i * dx / steps);
1469 sp2->to.y = (sp->current.y + i * dy / steps);
1470 sp2->to.w = (sp->current.w + i * dw / steps);
1471 sp2->to.h = (sp->current.h + i * dh / steps);
1472 sp2->current = sp2->from = sp2->to;
1473 sp2->duration = dur / steps;
1474 sp2->pause_duration += i * sp2->duration;
1475 sp2->remain_p = False;
1476 sp2->fatbits_p = True;
1479 sp2->duration += linger; /* last one lingers for a bit */
1481 if (i == steps && !blink_p)
1483 sp2->remain_p = sp->remain_p;
1484 sp2->fatbits_p = False;
1487 if (sp2->type == IMAGE && i > 0)
1488 push_flash_sprite (mi, sp2);
1490 if (sp2->type == RETICLE || sp2->type == BOX)
1492 sp2 = push_text_sprite (mi, sp2);
1494 sp2->duration += linger * 2;
1498 if (blink_p && blinger) /* last one blinks before vanishing */
1501 for (i = 1; i <= blinkers; i++)
1503 sprite *sp2 = copy_sprite (mi, sp);
1506 sp2->current = sp2->from = sp->to;
1507 sp2->duration = blinger / blinkers;
1508 sp2->pause_duration += dur + linger + i * sp2->duration;
1509 sp2->remain_p = False;
1512 sp2->remain_p = sp->remain_p;
1513 sp2->fatbits_p = False;
1518 /* Fade out the template sprite. It might not have even appeared yet. */
1519 fadeout_sprite (mi, sp);
1524 /* Input rect is of a reticle or box.
1525 Output rect is what the image's rect should be so that the only part
1526 visible is the part indicated by the input rect.
1529 compute_image_rect (rect *r, sprite *img, Bool inverse_p)
1531 double scale = (inverse_p ? 1/r->w : r->w);
1532 double dx = r->x - 0.5;
1533 double dy = r->y - 0.5;
1535 /* Adjust size and center by zoom factor */
1536 r->w = img->current.w / scale;
1537 r->h = img->current.h / scale;
1538 r->x = 0.5 + (img->current.x - 0.5) / scale;
1539 r->y = 0.5 + (img->current.y - 0.5) / scale;
1545 dx = -dx; /* #### Close but not quite right */
1554 /* Sets 'to' such that the image zooms out so that the only part visible
1555 is the part indicated by the box.
1558 track_box_with_image (ModeInfo *mi, sprite *sp, sprite *img)
1560 rect r = sp->current;
1561 compute_image_rect (&r, img, sp->back_p);
1564 /* Never zoom out too far. */
1565 if (img->to.w < 1 && img->to.h < 1)
1567 if (img->to.w > img->to.h)
1569 img->to.w = img->to.w / img->to.h;
1574 img->to.h = img->to.h / img->to.w;
1579 /* Never pan beyond the bounds of the image. */
1580 if (img->to.x < -img->to.w/2+1) img->to.x = -img->to.w/2+1;
1581 if (img->to.x > img->to.w/2) img->to.x = img->to.w/2;
1582 if (img->to.y < -img->to.h/2+1) img->to.y = -img->to.h/2+1;
1583 if (img->to.y > img->to.h/2) img->to.y = img->to.h/2;
1588 tick_animation (ModeInfo *mi)
1590 esper_state *ss = &sss[MI_SCREEN(mi)];
1591 anim_state prev_state = ss->anim_state;
1595 switch (ss->anim_state) {
1597 ss->anim_state = GRID_ON;
1600 ss->anim_state = IMAGE_LOAD;
1603 /* Only advance once an image has loaded. */
1604 if (find_newest_sprite (mi, IMAGE))
1605 ss->anim_state = RETICLE_ON;
1607 ss->anim_state = IMAGE_LOAD;
1610 ss->anim_state = RETICLE_MOVE;
1614 ss->anim_state = BOX_MOVE;
1616 ss->anim_state = IMAGE_ZOOM;
1619 ss->anim_state = IMAGE_ZOOM;
1623 sprite *sp = find_newest_sprite (mi, IMAGE);
1625 ? MIN (sp->current.w, sp->current.h)
1628 ss->anim_state = IMAGE_UNLOAD;
1630 ss->anim_state = RETICLE_ON;
1633 case IMAGE_FORCE_UNLOAD:
1634 ss->anim_state = IMAGE_UNLOAD;
1637 ss->anim_state = IMAGE_LOAD;
1640 ss->anim_state = MANUAL_BOX;
1644 case MANUAL_RETICLE_ON:
1645 ss->anim_state = MANUAL_RETICLE;
1647 case MANUAL_RETICLE:
1654 ss->anim_start = ss->now;
1655 ss->anim_duration = 0;
1658 fprintf (stderr, "%s: entering %s\n",
1659 progname, state_name (ss->anim_state));
1661 switch (ss->anim_state) {
1663 case GRID_ON: /* Start the grid fading in. */
1664 if (! find_newest_sprite (mi, GRID))
1666 sp = new_sprite (mi, GRID);
1668 sp->fade_duration = 1.0 / speed;
1669 sp->duration = 2.0 / speed;
1670 sp->remain_p = True;
1671 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1677 fadeout_sprites (mi, IMAGE);
1678 sp = new_sprite (mi, IMAGE);
1681 if (debug_p) fprintf (stderr, "%s: image load failed\n", progname);
1685 sp->fade_duration = 0.5 / speed;
1686 sp->duration = sp->fade_duration * 3;
1687 sp->remain_p = True;
1688 /* If we zoom in, we lose the pulse at the end. */
1689 /* sp->from.w = sp->from.h = 0.0001; */
1690 sp->current = sp->from;
1692 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1695 sp = push_text_sprite (mi, sp);
1696 sp->fade_duration = 0.2 / speed;
1697 sp->pause_duration = 0;
1698 sp->duration = 2.5 / speed;
1701 case IMAGE_FORCE_UNLOAD:
1705 sp = find_newest_sprite (mi, IMAGE);
1707 sp->fade_duration = ((prev_state == IMAGE_FORCE_UNLOAD ? 0.2 : 3.0)
1709 fadeout_sprites (mi, IMAGE);
1710 fadeout_sprites (mi, RETICLE);
1711 fadeout_sprites (mi, BOX);
1712 fadeout_sprites (mi, TEXT);
1713 ss->anim_duration = (sp ? sp->fade_duration : 0) + 3.5 / speed;
1716 case RETICLE_ON: /* Display reticle at center. */
1717 fadeout_sprites (mi, TEXT);
1718 sp = new_sprite (mi, RETICLE);
1720 sp->fade_duration = 0.2 / speed;
1721 sp->pause_duration = 1.0 / speed;
1722 sp->duration = 1.5 / speed;
1723 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1725 ss->anim_duration -= sp->fade_duration * 2;
1729 /* Reticle has faded in. Now move it to somewhere else.
1730 Create N new reticle sprites, wih staggered pause_durations.
1735 GLfloat nx, ny, dist;
1737 do { /* pick a new position not too near the old */
1738 nx = 0.3 + BELLRAND(0.4);
1739 ny = 0.3 + BELLRAND(0.4);
1740 dist = sqrt ((nx-ox)*(nx-ox) + (ny-oy)*(ny-oy));
1741 } while (dist < 0.1);
1743 sp = new_sprite (mi, RETICLE);
1748 sp->current = sp->to = sp->from;
1751 sp->fade_duration = 0.2 / speed;
1752 sp->pause_duration = 0;
1753 compute_sprite_duration (mi, sp, False);
1755 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1756 sp->duration - 0.1);
1757 animate_sprite_path (mi, sp, False);
1762 /* Reticle has moved, and faded out.
1763 Start the box zooming into place.
1771 /* Find the last-added reticle, for our destination position. */
1773 for (i = 0; i < ss->nsprites; i++)
1775 sprite *sp2 = ss->sprites[i];
1776 if (sp2->type == RETICLE &&
1777 (!sp || sp->start_time < sp2->start_time))
1789 fprintf (stderr, "%s: no reticle before box?\n", progname);
1792 z = 0.3 + frand(0.5);
1794 /* Ensure that the selected box is contained within the screen */
1796 double margin = 0.005;
1797 double maxw = 2 * MIN (1 - margin - nx, nx - margin);
1798 double maxh = 2 * MIN (1 - margin - ny, ny - margin);
1799 double max = MIN (maxw, maxh);
1800 if (z > max) z = max;
1803 sp = new_sprite (mi, BOX);
1809 sp->current = sp->from;
1815 /* Maybe zoom out instead of in.
1818 sprite *img = find_newest_sprite (mi, IMAGE);
1819 double depth = MIN (img->current.w, img->current.h);
1820 if (depth > 1 && /* if zoomed in */
1821 (depth < 6 ? !(random() % 5) : /* 20% */
1822 depth < 12 ? !(random() % 2) : /* 50% */
1823 (random() % 3))) /* 66% */
1826 if (depth < 1.5 && z < 0.8)
1828 z = 0.8; /* don't zoom out much past 100% */
1835 sp->fade_duration = 0.2 / speed;
1836 sp->pause_duration = 2.0 / speed;
1837 compute_sprite_duration (mi, sp, True);
1838 ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
1839 sp->duration - 0.1);
1840 animate_sprite_path (mi, sp, True);
1846 /* Box has moved, and faded out.
1847 Or, if no box, then just a reticle.
1848 Zoom the underlying image to track the box's position. */
1852 /* Find latest box or reticle, for our destination position. */
1853 sp = find_newest_sprite (mi, BOX);
1855 sp = find_newest_sprite (mi, RETICLE);
1859 fprintf (stderr, "%s: no box or reticle before image\n",
1864 img = find_newest_sprite (mi, IMAGE);
1868 fprintf (stderr, "%s: no image?\n", progname);
1872 img2 = copy_sprite (mi, img);
1875 img2->from = img->current;
1877 fadeout_sprite (mi, img);
1879 track_box_with_image (mi, sp, img2);
1881 img2->fade_duration = 0.2 / speed;
1882 img2->pause_duration = 0.5 / speed;
1883 img2->remain_p = True;
1884 img2->throb_p = False;
1885 compute_sprite_duration (mi, img2, False);
1887 img->start_time += img2->pause_duration;
1889 ss->anim_duration = (img2->pause_duration + img2->fade_duration * 2 +
1891 animate_sprite_path (mi, img2, False);
1892 fadeout_sprites (mi, TEXT);
1897 case MANUAL_RETICLE_ON:
1901 case MANUAL_RETICLE:
1903 sprite_type tt = (ss->anim_state == MANUAL_BOX ? BOX : RETICLE);
1904 sprite *osp = find_newest_sprite (mi, tt);
1905 fadeout_sprites (mi, RETICLE);
1906 fadeout_sprites (mi, BOX);
1908 sp = new_sprite (mi, tt);
1911 sp->from = osp->current;
1919 sp->to = sp->current = sp->from;
1920 sp->fade_duration = 0.2 / speed;
1921 sp->duration = 0.2 / speed;
1922 sp->remain_p = True;
1923 sp->throb_p = False;
1924 ss->anim_duration = 9999;
1929 fprintf (stderr,"%s: unknown state %d\n",
1930 progname, (int) ss->anim_state);
1936 /* Copied from gltrackball.c, sigh.
1939 adjust_for_device_rotation (double *x, double *y, double *w, double *h)
1941 int rot = (int) current_device_rotation();
1944 while (rot <= -180) rot += 360;
1945 while (rot > 180) rot -= 360;
1947 if (rot > 135 || rot < -135) /* 180 */
1952 else if (rot > 45) /* 90 */
1954 swap = *x; *x = *y; *y = swap;
1955 swap = *w; *w = *h; *h = swap;
1958 else if (rot < -45) /* 270 */
1960 swap = *x; *x = *y; *y = swap;
1961 swap = *w; *w = *h; *h = swap;
1968 esper_handle_event (ModeInfo *mi, XEvent *event)
1970 esper_state *ss = &sss[MI_SCREEN(mi)];
1972 if (event->xany.type == Expose ||
1973 event->xany.type == GraphicsExpose ||
1974 event->xany.type == VisibilityNotify)
1978 else if (event->xany.type == KeyPress)
1983 double delta = 0.025;
1984 double margin = 0.005;
1987 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1991 ss->anim_state = IMAGE_FORCE_UNLOAD;
1995 if (! find_newest_sprite (mi, IMAGE))
1996 return False; /* Too early */
1998 ss->now = double_time();
2000 # define BONK() do { \
2001 if (ss->anim_state != MANUAL_BOX_ON && \
2002 ss->anim_state != MANUAL_BOX) { \
2003 ss->anim_state = MANUAL_BOX_ON; \
2004 tick_animation (mi); \
2006 sp = find_newest_sprite (mi, BOX); \
2007 if (!sp) abort() ; \
2008 sp->from = sp->current; \
2009 sp->to = sp->from; \
2010 sp->start_time = ss->now - sp->fade_duration; \
2014 if (keysym == XK_Left || c == ',' || c == '<')
2019 else if (keysym == XK_Right || c == '.' || c == '>')
2024 else if (keysym == XK_Down || c == '-')
2029 else if (keysym == XK_Up || c == '=')
2032 sp->to.y += delta, ok = True;
2034 else if (keysym == XK_Prior || c == '+')
2038 sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2040 else if (keysym == XK_Next || c == '_')
2044 sp->to.h = sp->to.w * sp->from.w / sp->from.h;
2046 else if ((c == ' ' || c == '\t') && debug_p &&
2047 ss->anim_state == MANUAL_BOX)
2049 BONK(); /* Null motion: just flash the current image. */
2051 else if ((keysym == XK_Home || c == ' ' || c == '\t') &&
2052 ss->anim_state == MANUAL_BOX)
2060 else if ((c == '\r' || c == '\n' || c == 033) &&
2061 ss->anim_state == MANUAL_BOX)
2064 ss->anim_state = BOX_MOVE;
2065 ss->anim_duration = 9999;
2066 ss->anim_start = ss->now - ss->anim_duration;
2067 fadeout_sprite (mi, sp);
2076 /* Keep it on screen */
2077 if (sp->to.w > 1 - margin)
2079 GLfloat r = sp->to.h / sp->to.w;
2080 sp->to.w = 1-margin;
2081 sp->to.h = (1-margin) * r;
2085 GLfloat r = sp->to.h / sp->to.w;
2086 sp->to.w = (1-margin) / r;
2087 sp->to.h = 1-margin;
2090 if (sp->to.x - sp->to.w/2 < margin)
2091 sp->to.x = sp->to.w/2 + margin;
2092 if (sp->to.y - sp->to.h/2 < margin)
2093 sp->to.y = sp->to.h/2 + margin;
2095 if (sp->to.x + sp->to.w/2 >= 1 + margin)
2096 sp->to.x = 1 - (sp->to.w/2 + margin);
2097 if (sp->to.y + sp->to.h/2 >= 1 + margin)
2098 sp->to.y = 1 - (sp->to.h/2 + margin);
2100 /* Now let's give a momentary glimpse of what the image would do. */
2106 /* Find the lingering image */
2107 /* img = find__sprite (mi, IMAGE); */
2108 for (i = 0; i < ss->nsprites; i++)
2110 sprite *sp2 = ss->sprites[i];
2111 if (sp2->type == IMAGE &&
2114 (img->start_time < sp2->start_time &&
2115 ss->now >= sp2->start_time + sp2->pause_duration)))
2120 img = copy_sprite (mi, img);
2121 img->pause_duration = 0;
2122 img->fade_duration = 0.1 / speed;
2123 img->duration = 0.5 / speed;
2124 img->start_time = ss->now;
2125 img->remain_p = False;
2126 track_box_with_image (mi, sp, img);
2127 img->from = img->current = img->to;
2132 else if (event->xany.type == ButtonPress &&
2133 event->xbutton.button == Button1)
2135 ss->button_down_p = 1;
2138 else if (event->xany.type == ButtonRelease &&
2139 event->xbutton.button == Button1)
2141 ss->button_down_p = 0;
2143 if (ss->anim_state == MANUAL_BOX)
2145 sprite *sp = find_newest_sprite (mi, BOX);
2146 if (sp) fadeout_sprite (mi, sp);
2147 ss->anim_state = BOX_MOVE;
2148 ss->anim_duration = 9999;
2149 ss->anim_start = ss->now - ss->anim_duration;
2151 else if (ss->anim_state == MANUAL_RETICLE)
2153 sprite *sp = find_newest_sprite (mi, RETICLE);
2154 if (sp) fadeout_sprite (mi, sp);
2155 ss->anim_state = RETICLE_MOVE;
2156 ss->anim_duration = 9999;
2157 ss->anim_start = ss->now - ss->anim_duration;
2161 else if (event->xany.type == MotionNotify &&
2162 ss->button_down_p &&
2163 (ss->anim_state == MANUAL_RETICLE ||
2164 ss->anim_state == RETICLE_MOVE))
2167 double x = event->xmotion.x;
2168 double y = event->xmotion.y;
2169 double w = MI_WIDTH(mi);
2170 double h = MI_HEIGHT(mi);
2172 adjust_for_device_rotation (&x, &y, &w, &h);
2176 if (ss->anim_state != MANUAL_RETICLE_ON &&
2177 ss->anim_state != MANUAL_RETICLE)
2179 ss->anim_state = MANUAL_RETICLE_ON;
2180 tick_animation (mi);
2182 sp = find_newest_sprite (mi, RETICLE);
2184 sp->from = sp->current;
2186 sp->start_time = ss->now - sp->fade_duration;
2187 sp->remain_p = True;
2189 sp->current.x = MIN (0.95, MAX (0.05, x));
2190 sp->current.y = MIN (0.95, MAX (0.05, y));
2191 sp->from = sp->to = sp->current;
2193 /* Don't update the text sprite more often than once a second. */
2195 sprite *sp2 = find_newest_sprite (mi, TEXT);
2196 if (!sp2 || sp2->start_time < ss->now-1)
2198 fadeout_sprites (mi, TEXT);
2199 sp = push_text_sprite (mi, sp);
2200 sp->remain_p = True;
2206 else if (event->xany.type == MotionNotify &&
2207 ss->button_down_p &&
2208 (ss->anim_state == MANUAL_BOX ||
2209 ss->anim_state == BOX_MOVE))
2212 double x = event->xmotion.x;
2213 double y = event->xmotion.y;
2214 double w = MI_WIDTH(mi);
2215 double h = MI_HEIGHT(mi);
2219 adjust_for_device_rotation (&x, &y, &w, &h);
2224 max = (2 * (0.5 - MAX (fabs (sp->current.x - 0.5),
2225 fabs (sp->current.y - 0.5)))
2228 x = fabs (x - sp->current.x);
2229 y = fabs (y - sp->current.y);
2232 sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*x));
2234 sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*y));
2235 sp->from = sp->to = sp->current;
2237 /* Don't update the text sprite more often than once a second. */
2239 sprite *sp2 = find_newest_sprite (mi, TEXT);
2240 if (!sp2 || sp2->start_time < ss->now-1)
2242 fadeout_sprites (mi, TEXT);
2243 sp = push_text_sprite (mi, sp);
2244 sp->remain_p = True;
2250 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
2252 ss->anim_state = IMAGE_FORCE_UNLOAD;
2262 reshape_esper (ModeInfo *mi, int width, int height)
2266 glViewport (0, 0, width, height);
2267 glMatrixMode (GL_PROJECTION);
2269 glRotatef (current_device_rotation(), 0, 0, 1);
2270 glMatrixMode (GL_MODELVIEW);
2278 if (s < 0.1) s = 0.1;
2282 glTranslatef (-0.5, -0.5, 0);
2284 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2286 /* Stretch each existing image to match new window aspect. */
2288 esper_state *ss = &sss[MI_SCREEN(mi)];
2290 for (i = 0; i < ss->nsprites; i++)
2292 sprite *sp = ss->sprites[i];
2293 if (sp && sp->type == IMAGE && sp->img && sp->img->loaded_p)
2295 GLfloat sp_asp = sp->current.h / sp->current.w;
2296 GLfloat img_asp = (sp->img->geom.height /
2297 (GLfloat) sp->img->geom.width);
2298 GLfloat new_win = (MI_WIDTH(mi) / (double) MI_HEIGHT(mi));
2299 GLfloat old_win = sp_asp / img_asp;
2300 GLfloat r = old_win / new_win;
2320 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
2323 char *string = get_string_resource (mi->dpy, key, "EsperColor");
2324 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
2326 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
2331 color[0] = xcolor.red / 65536.0;
2332 color[1] = xcolor.green / 65536.0;
2333 color[2] = xcolor.blue / 65536.0;
2339 init_esper (ModeInfo *mi)
2341 int screen = MI_SCREEN(mi);
2343 int wire = MI_IS_WIREFRAME(mi);
2348 if ((ss->glx_context = init_GL(mi)) != NULL) {
2349 reshape_esper (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2354 parse_color (mi, "gridColor", ss->grid_color);
2355 parse_color (mi, "reticleColor", ss->reticle_color);
2356 parse_color (mi, "textColor", ss->text_color);
2358 glDisable (GL_LIGHTING);
2359 glDisable (GL_DEPTH_TEST);
2360 glDepthMask (GL_FALSE);
2361 glEnable (GL_CULL_FACE);
2362 glCullFace (GL_BACK);
2366 glEnable (GL_TEXTURE_2D);
2367 glShadeModel (GL_SMOOTH);
2368 glEnable (GL_BLEND);
2369 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2372 ss->font_data = load_texture_font (mi->dpy, "titleFont");
2374 ss->now = double_time();
2375 ss->dawn_of_time = ss->now;
2379 ss->anim_state = BLANK;
2381 ss->anim_duration = 0;
2386 draw_esper (ModeInfo *mi)
2388 esper_state *ss = &sss[MI_SCREEN(mi)];
2390 if (!ss->glx_context)
2393 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
2395 mi->polygon_count = 0;
2397 ss->now = double_time();
2401 if (ss->now >= ss->anim_start + ss->anim_duration)
2402 tick_animation (mi);
2404 if (mi->fps_p) do_fps (mi);
2407 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
2410 XSCREENSAVER_MODULE ("Esper", esper)