summaryrefslogtreecommitdiffstats
path: root/hacks/glx/esper.c
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/glx/esper.c
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'hacks/glx/esper.c')
-rw-r--r--hacks/glx/esper.c2412
1 files changed, 2412 insertions, 0 deletions
diff --git a/hacks/glx/esper.c b/hacks/glx/esper.c
new file mode 100644
index 0000000..7a0bb0b
--- /dev/null
+++ b/hacks/glx/esper.c
@@ -0,0 +1,2412 @@
+/* esper, Copyright (c) 2017-2018 Jamie Zawinski <jwz@jwz.org>
+ * Enhance 224 to 176. Pull out track right. Center in pull back.
+ * Pull back. Wait a minute. Go right. Stop. Enhance 57 19. Track 45 left.
+ * Gimme a hardcopy right there.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/*
+ The Esper machine has a 4:3 display, about 12" diagonal.
+ The display is overlayed with a 10x7 grid of blue lines.
+ The scene goes approximately like this:
+
+ "Enhance 224 To 176."
+
+ ZM 0000 NS 0000 EW 0000
+
+ The reticle is displayed centered.
+ It moves in 8 steps with 3 frame blur to move to around to grid 1,4.
+
+ ZM 0000 NS 0000 EW 0000
+ ZM 0000 NS 0001 EW 0001
+ ZM 0000 NS 0001 EW 0002
+ ZM 0000 NS 0002 EW 0003
+ ZM 0000 NS 0003 EW 0005
+ ZM 0000 NS 0004 EW 0008
+ ZM 0000 NS 0015 EW 0011
+
+ These numbers appear to have little relation to what we are
+ actually seeing on the screen. Also the same numbers are
+ repeated later when looking at totally different parts of
+ the photograph.
+
+ ZM 0000 NS 0117 EW 0334
+
+ The box appears: 8 steps, final box is 1.5x2.25 at -0.5,4.0.
+
+ ZM 4086 NS 0117 EW 0334
+
+ The box blinks yellow 5x.
+ The image's zoom-and-pan takes 8 steps, with no text on the screen.
+ The zoom is in discreet steps, with flashes.
+ The grid stays the same size the whole time.
+ The flashes look like solarization to blue.
+ When the zoom is finished, there is still no text.
+
+ "Enhance." Goes 4 more ticks down the same hole?
+ "Stop." Moves up a little bit at the end.
+
+ Then with no instructions, it goes 20 ticks by itself, off camera.
+
+ "Move in." 10 ticks.
+ "Stop." (We are looking at a fist in the picture.)
+ "Pull out track right."
+ "Stop." (We are looking at a newspaper.)
+ "Center and pull back."
+ "Stop." (We just passed the round mirror.)
+ "Track 45 right."
+ "Stop."
+ "Center and stop."
+
+ This time there was no grid until it stopped, then the grid showed up.
+ There is video tearing at the bottom.
+
+ "Enhance 34 to 36."
+
+ ZM 0000 NS 0063 EW 0185
+ ZM 0000 NS 0197 EW 0334
+ ZM 3841 NS 0197 EW 0334
+
+ It kind of zooms in to the center wobbly and willy-nilly.
+ We are now looking at a glass.
+
+ "Pan right and pull back." (There is no grid while moving again.)
+ "Stop."
+
+ Ok, at this point, we enter fantasy-land. From here on, the images
+ shown are very high resolution with no noise. And suddenly the
+ UI on the Esper is *way* higher resolution. My theory is that from
+ this point on in the scene, we are not looking at the literal Esper
+ machine, but instead the movie is presenting Decard's perception of
+ it. We're seeing the room, not the photo of the room. The map has
+ become the territory.
+
+ "Enhance 34 to 46."
+
+ ZM 0000 NS 0197 EW 0334
+
+ This has the reticle and box only, no grid, ends with no grid.
+
+ "Pull back."
+ "Wait a minute. Go right."
+ "Stop."
+ Now it's going around the corner or something.
+
+ "Enhance 57 19."
+ This has a reticle then box, but the image started zooming early.
+
+ "Track 45 left."
+ zooms out and moves left
+
+ "Stop." (O hai Zhora.)
+ "Enhance 15 to 23."
+
+ ZM 3852 NS 0197 EW 0334
+
+ "Gimme a hardcopy right there."
+
+ The printer polaroid is WAY lower resolution than the image we see on
+ the "screen" -- in keeping with my theory that we were not seeing the
+ screen.
+
+
+ TODO:
+
+ * There's a glitch at the top/bottom of the texfont textures.
+ * "Pull back" isn't quite symmetric: zoom origin is slightly off.
+ * Maybe display text like "Pull right" and "Stop".
+*/
+
+
+/* Use a small point size to keep it nice and grainy. */
+#if defined(HAVE_COCOA) || defined(HAVE_ANDROID)
+# define TITLE_FONT "OCR A Std 10, Lucida Console 10, Monaco 10"
+#elif 0 /* real X11, XQueryFont() */
+# define TITLE_FONT "-*-courier-bold-r-*-*-*-100-*-*-m-*-*-*"
+#else /* real X11, load_font_retry() */
+# define TITLE_FONT "-*-ocr a std-medium-r-*-*-*-100-*-*-m-*-*-*"
+#endif
+
+#define DEFAULTS "*delay: 20000 \n" \
+ "*wireframe: False \n" \
+ "*showFPS: False \n" \
+ "*fpsTop: True \n" \
+ "*useSHM: True \n" \
+ "*titleFont: " TITLE_FONT "\n" \
+ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
+ "*grabDesktopImages: False \n" \
+ "*chooseRandomImages: True \n" \
+ "*gridColor: #4444FF\n" \
+ "*reticleColor: #FFFF77\n" \
+ "*textColor: #FFFFBB\n" \
+
+# define free_esper 0
+# define refresh_esper 0
+# define release_esper 0
+# include "xlockmore.h"
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef RANDSIGN
+#define RANDSIGN() ((random() & 1) ? 1 : -1)
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+#ifdef USE_GL
+
+#undef SMOOTH
+
+# define DEF_GRID_SIZE "11"
+# define DEF_GRID_THICKNESS "15"
+# define DEF_TITLES "True"
+# define DEF_SPEED "1.0"
+# define DEF_DEBUG "False"
+
+#include "grab-ximage.h"
+#include "texfont.h"
+
+#ifdef HAVE_XSHM_EXTENSION
+# include "xshm.h" /* to get <sys/shm.h> */
+#endif
+
+
+typedef struct {
+ double x, y, w, h;
+} rect;
+
+typedef struct {
+ ModeInfo *mi;
+ unsigned long id; /* unique */
+ char *title; /* the filename of this image */
+ int w, h; /* size in pixels of the image */
+ int tw, th; /* size in pixels of the texture */
+ XRectangle geom; /* where in the image the bits are */
+ Bool loaded_p; /* whether the image has finished loading */
+ Bool used_p; /* whether the image has yet appeared
+ on screen */
+ GLuint texid; /* which texture contains the image */
+ int refcount; /* how many sprites refer to this image */
+} image;
+
+
+typedef enum {
+ BLANK,
+ GRID_ON,
+ IMAGE_LOAD,
+ IMAGE_UNLOAD,
+ IMAGE_FORCE_UNLOAD,
+ REPOSITION,
+ RETICLE_ON,
+ RETICLE_MOVE,
+ BOX_MOVE,
+ IMAGE_ZOOM,
+ MANUAL_RETICLE_ON,
+ MANUAL_RETICLE,
+ MANUAL_BOX_ON,
+ MANUAL_BOX,
+} anim_state;
+
+typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
+typedef enum { IMAGE, RETICLE, BOX, GRID, FLASH, TEXT } sprite_type;
+
+typedef struct {
+ unsigned long id; /* unique */
+ sprite_type type;
+ image *img; /* type = IMAGE */
+ unsigned long text_id; /* type = TEXT */
+ char *text;
+ GLfloat opacity;
+ GLfloat thickness_scale; /* line and image types */
+ Bool throb_p;
+ double start_time; /* when this animation began */
+ double duration; /* lifetime of sprite in seconds; 0 = inf */
+ double fade_duration; /* speed of fade in and fade out */
+ double pause_duration; /* delay before fade-in starts */
+ Bool remain_p; /* pause forever before fade-out */
+ rect from, to, current; /* the journey this image is taking */
+ sprite_state state; /* the state we're in right now */
+ double state_time; /* time of last state change */
+ int frame_count; /* frames since last state change */
+ Bool fatbits_p; /* For image texture rendering */
+ Bool back_p; /* If BOX, zooming out, not in */
+} sprite;
+
+
+typedef struct {
+ GLXContext *glx_context;
+ int nimages; /* how many images are loaded or loading now */
+ image *images[10]; /* pointers to the images */
+
+ int nsprites; /* how many sprites are animating right now */
+ sprite *sprites[100]; /* pointers to the live sprites */
+
+ double now; /* current time in seconds */
+ double dawn_of_time; /* when the program launched */
+ double image_load_time; /* time when we last loaded a new image */
+
+ texture_font_data *font_data;
+
+ int sprite_id, image_id; /* debugging id counters */
+
+ GLfloat grid_color[4], reticle_color[4], text_color[4];
+
+ anim_state anim_state; /* Counters for global animation state, */
+ double anim_start, anim_duration;
+
+ Bool button_down_p;
+
+} esper_state;
+
+static esper_state *sss = NULL;
+
+
+/* Command-line arguments
+ */
+static int grid_size;
+static int grid_thickness;
+
+static Bool do_titles; /* Display image titles. */
+static GLfloat speed;
+static Bool debug_p; /* Be loud and do weird things. */
+
+
+static XrmOptionDescRec opts[] = {
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-titles", ".titles", XrmoptionNoArg, "True" },
+ { "-no-titles", ".titles", XrmoptionNoArg, "False" },
+ { "-debug", ".debug", XrmoptionNoArg, "True" },
+};
+
+static argtype vars[] = {
+ { &grid_size, "gridSize", "GridSize", DEF_GRID_SIZE, t_Int},
+ { &grid_thickness,"gridThickness","GridThickness",DEF_GRID_THICKNESS, t_Int},
+ { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
+ { &speed, "speed", "Speed", DEF_SPEED, t_Float},
+ { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
+};
+
+ENTRYPOINT ModeSpecOpt esper_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Returns the current time in seconds as a double.
+ */
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+static const char *
+state_name (anim_state s)
+{
+ switch (s) {
+ case BLANK: return "BLANK";
+ case GRID_ON: return "GRID_ON";
+ case IMAGE_LOAD: return "IMAGE_LOAD";
+ case IMAGE_UNLOAD: return "IMAGE_UNLOAD";
+ case IMAGE_FORCE_UNLOAD: return "IMAGE_FORCE_UNLOAD";
+ case REPOSITION: return "REPOSITION";
+ case RETICLE_ON: return "RETICLE_ON";
+ case RETICLE_MOVE: return "RETICLE_MOVE";
+ case BOX_MOVE: return "BOX_MOVE";
+ case IMAGE_ZOOM: return "IMAGE_ZOOM";
+ case MANUAL_BOX_ON: return "MANUAL_BOX_ON";
+ case MANUAL_BOX: return "MANUAL_BOX";
+ case MANUAL_RETICLE_ON: return "MANUAL_RETICLE_ON";
+ case MANUAL_RETICLE: return "MANUAL_RETICLE";
+ default: return "UNKNOWN";
+ }
+}
+
+
+static void image_loaded_cb (const char *filename, XRectangle *geom,
+ int image_width, int image_height,
+ int texture_width, int texture_height,
+ void *closure);
+
+
+/* Allocate an image structure and start a file loading in the background.
+ */
+static image *
+alloc_image (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ image *img = (image *) calloc (1, sizeof (*img));
+
+ img->id = ++ss->image_id;
+ img->loaded_p = False;
+ img->used_p = False;
+ img->mi = mi;
+
+ glGenTextures (1, &img->texid);
+ if (img->texid <= 0) abort();
+
+ ss->image_load_time = ss->now;
+
+ if (wire)
+ image_loaded_cb (0, 0, 0, 0, 0, 0, img);
+ else
+ {
+ /* If possible, load images at much higher resolution than the window,
+ to facilitate deep zooms.
+ */
+ int max_max = 4096; /* ~12 megapixels */
+ int max = 0;
+
+# if defined(HAVE_XSHM_EXTENSION) && \
+ !defined(HAVE_MOBILE) && \
+ !defined(HAVE_COCOA)
+
+ /* Try not to ask for an image larger than the SHM segment size.
+ If XSHM fails in a real-X11 world, it can take a staggeringly long
+ time to transfer the image bits from the server over Xproto -- like,
+ *18 seconds* for 4096 px and 8 seconds for 3072 px on MacOS XQuartz.
+ What madness is this?
+ */
+ unsigned long shmmax = 0;
+
+# if defined(SHMMAX)
+ /* Linux 2.6 defines this to be 0x2000000, but on CentOS 6.9,
+ "sysctl kernel.shmmax" reports a luxurious 0x1000000000. */
+ shmmax = SHMMAX;
+# elif defined(__APPLE__)
+ /* MacOS 10.13 "sysctl kern.sysv.shmmax" is paltry: */
+ shmmax = 0x400000;
+# endif /* !SHMMAX */
+
+ if (shmmax)
+ {
+ /* Roughly, bytes => NxN. b = (n/8)*4n = n*n*4, so n^2 = 2b, so: */
+ unsigned long n = sqrt(shmmax)/2;
+ if (n < max_max)
+ max_max = n;
+ }
+# endif /* HAVE_XSHM_EXTENSION and real X11 */
+
+ glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max);
+ if (max > max_max) max = max_max;
+
+ /* Never ask for an image smaller than the window, even if that
+ will make XSHM fall back to Xproto. */
+ if (max < MI_WIDTH(mi) || max < MI_HEIGHT(mi))
+ max = 0;
+
+ load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
+ max, max, False, img->texid, image_loaded_cb, img);
+ }
+
+ ss->images[ss->nimages++] = img;
+ if (ss->nimages >= countof(ss->images)) abort();
+
+ return img;
+}
+
+
+/* Callback that tells us that the texture has been loaded.
+ */
+static void
+image_loaded_cb (const char *filename, XRectangle *geom,
+ int image_width, int image_height,
+ int texture_width, int texture_height,
+ void *closure)
+{
+ image *img = (image *) closure;
+ ModeInfo *mi = img->mi;
+ int ow, oh;
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+
+ int wire = MI_IS_WIREFRAME(mi);
+
+ if (wire)
+ {
+ img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
+ img->h = MI_HEIGHT (mi);
+ img->geom.width = img->w;
+ img->geom.height = img->h;
+ goto DONE;
+ }
+
+ if (image_width == 0 || image_height == 0)
+ exit (1);
+
+ img->w = image_width;
+ img->h = image_height;
+ img->tw = texture_width;
+ img->th = texture_height;
+ img->geom = *geom;
+ img->title = (filename ? strdup (filename) : 0);
+
+ ow = img->geom.width;
+ oh = img->geom.height;
+
+ /* If the image's width doesn't come back as the width of the screen,
+ then the image must have been scaled down (due to insufficient
+ texture memory.) Scale up the coordinates to stretch the image
+ to fill the window.
+ */
+ if (img->w != MI_WIDTH(mi))
+ {
+ double scale = (double) MI_WIDTH(mi) / img->w;
+ img->w *= scale;
+ img->h *= scale;
+ img->tw *= scale;
+ img->th *= scale;
+ img->geom.x *= scale;
+ img->geom.y *= scale;
+ img->geom.width *= scale;
+ img->geom.height *= scale;
+ }
+
+ /* xscreensaver-getimage returns paths relative to the image directory
+ now, so leave the sub-directory part in. Unless it's an absolute path.
+ */
+ if (img->title && img->title[0] == '/')
+ {
+ /* strip filename to part between last "/" and last ".". */
+ char *s = strrchr (img->title, '/');
+ if (s) strcpy (img->title, s+1);
+ s = strrchr (img->title, '.');
+ if (s) *s = 0;
+ }
+
+# if !(__APPLE__ && TARGET_IPHONE_SIMULATOR || !defined(__OPTIMIZE__))
+ if (debug_p)
+# endif
+ fprintf (stderr, "%s: loaded %lu \"%s\" %dx%d\n",
+ progname, img->id, (img->title ? img->title : "(null)"),
+ ow, oh);
+ DONE:
+
+ img->loaded_p = True;
+}
+
+
+
+/* Free the image and texture, after nobody is referencing it.
+ */
+static void
+destroy_image (ModeInfo *mi, image *img)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ Bool freed_p = False;
+ int i;
+
+ if (!img) abort();
+ if (!img->loaded_p) abort();
+ if (!img->used_p) abort();
+ if (img->texid <= 0) abort();
+ if (img->refcount != 0) abort();
+
+ for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
+ if (ss->images[i] == img)
+ {
+ int j;
+ for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
+ ss->images[j] = ss->images[j+1];
+ ss->images[j] = 0;
+ ss->nimages--;
+ freed_p = True;
+ break;
+ }
+
+ if (!freed_p) abort();
+
+ if (debug_p)
+ fprintf (stderr, "%s: unloaded img %2lu: \"%s\"\n",
+ progname, img->id, (img->title ? img->title : "(null)"));
+
+ if (img->title) free (img->title);
+ glDeleteTextures (1, &img->texid);
+ free (img);
+}
+
+
+/* Return an image to use for a sprite.
+ If it's time for a new one, get a new one.
+ Otherwise, use an old one.
+ Might return 0 if the machine is really slow.
+ */
+static image *
+get_image (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = 0;
+ image *loading_img = 0;
+ int i;
+
+ for (i = 0; i < ss->nimages; i++)
+ {
+ image *img2 = ss->images[i];
+ if (!img2) abort();
+ if (!img2->loaded_p)
+ loading_img = img2;
+ else
+ img = img2;
+ }
+
+ /* Make sure that there is always one unused image in the pipe.
+ */
+ if (!img && !loading_img)
+ alloc_image (mi);
+
+ return img;
+}
+
+
+/* Allocate a new sprite and start its animation going.
+ */
+static sprite *
+new_sprite (ModeInfo *mi, sprite_type type)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = (type == IMAGE ? get_image (mi) : 0);
+ sprite *sp;
+
+ if (type == IMAGE && !img)
+ {
+ /* Oops, no images yet! The machine is probably hurting bad.
+ Let's give it some time before thrashing again. */
+ usleep (250000);
+ return 0;
+ }
+
+ sp = (sprite *) calloc (1, sizeof (*sp));
+ sp->id = ++ss->sprite_id;
+ sp->type = type;
+ sp->start_time = ss->now;
+ sp->state_time = sp->start_time;
+ sp->thickness_scale = 1;
+ sp->throb_p = True;
+ sp->to.x = 0.5;
+ sp->to.y = 0.5;
+ sp->to.w = 1.0;
+ sp->to.h = 1.0;
+
+ if (img)
+ {
+ sp->img = img;
+ sp->img->refcount++;
+ sp->img->used_p = True;
+ sp->duration = 0; /* forever, until further notice */
+ sp->fade_duration = 0.5;
+
+ /* Scale the sprite so that the image bits fill the window. */
+ {
+ double w = MI_WIDTH(mi);
+ double h = MI_HEIGHT(mi);
+ double r;
+ r = ((img->geom.height / (double) img->geom.width) * (w / h));
+ if (r > 1)
+ sp->to.h *= r;
+ else
+ sp->to.w /= r;
+ }
+
+ /* Pan to a random spot */
+ if (sp->to.h > 1)
+ sp->to.y += frand ((sp->to.h - 1) / 2) * RANDSIGN();
+ if (sp->to.w > 1)
+ sp->to.x += frand ((sp->to.w - 1) / 2) * RANDSIGN();
+ }
+
+ sp->from = sp->current = sp->to;
+
+ ss->sprites[ss->nsprites++] = sp;
+ if (ss->nsprites >= countof(ss->sprites)) abort();
+
+ return sp;
+}
+
+
+static sprite *
+copy_sprite (ModeInfo *mi, sprite *old)
+{
+ sprite *sp = new_sprite (mi, (sprite_type) ~0L);
+ int id;
+ double tt = sp->start_time;
+ if (!sp) abort();
+ id = sp->id;
+ memcpy (sp, old, sizeof(*sp));
+ sp->id = id;
+ sp->state = NEW;
+ sp->state_time = sp->start_time = tt;
+ if (sp->img)
+ sp->img->refcount++;
+ return sp;
+}
+
+
+/* Free the given sprite, and decrement the reference count on its image.
+ */
+static void
+destroy_sprite (ModeInfo *mi, sprite *sp)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ Bool freed_p = False;
+ image *img;
+ int i;
+
+ if (!sp) abort();
+ if (sp->state != DEAD) abort();
+ img = sp->img;
+
+ if (sp->type != IMAGE)
+ {
+ if (img) abort();
+ }
+ else
+ {
+ if (!img) abort();
+ if (!img->loaded_p) abort();
+ if (!img->used_p) abort();
+ if (img->refcount <= 0) abort();
+ }
+
+ for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
+ if (ss->sprites[i] == sp)
+ {
+ int j;
+ for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
+ ss->sprites[j] = ss->sprites[j+1];
+ ss->sprites[j] = 0;
+ ss->nsprites--;
+ freed_p = True;
+ break;
+ }
+
+ if (!freed_p) abort();
+ if (sp->text) free (sp->text);
+ free (sp);
+ sp = 0;
+
+ if (img)
+ {
+ img->refcount--;
+ if (img->refcount < 0) abort();
+ if (img->refcount == 0)
+ destroy_image (mi, img);
+ }
+}
+
+
+/* Updates the sprite for the current frame of the animation based on
+ its creation time compared to the current wall clock.
+ */
+static void
+tick_sprite (ModeInfo *mi, sprite *sp)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = sp->img;
+ double now = ss->now;
+ double secs;
+ double ratio;
+ GLfloat visible = sp->duration + sp->fade_duration * 2;
+ GLfloat total = sp->pause_duration + visible;
+
+ if (sp->type != IMAGE)
+ {
+ if (sp->img) abort();
+ }
+ else
+ {
+ if (! sp->img) abort();
+ if (! img->loaded_p) abort();
+ }
+
+ /* pause fade duration fade
+ |------------|------------|---------|-----------|
+ ....----====##########====----....
+ from current to
+ */
+
+ secs = now - sp->start_time;
+ ratio = (visible <= 0 ? 1 : ((secs - sp->pause_duration) / visible));
+ if (ratio < 0) ratio = 0;
+ else if (ratio > 1) ratio = 1;
+
+ sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
+ sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
+ sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
+ sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
+
+ sp->thickness_scale = 1;
+
+ if (secs < sp->pause_duration)
+ {
+ sp->state = IN;
+ sp->opacity = 0;
+ }
+ else if (secs < sp->pause_duration + sp->fade_duration)
+ {
+ sp->state = IN;
+ sp->opacity = (secs - sp->pause_duration) / (GLfloat) sp->fade_duration;
+ }
+ else if (sp->duration == 0 || /* 0 means infinite lifetime */
+ sp->remain_p ||
+ secs < sp->pause_duration + sp->fade_duration + sp->duration)
+ {
+ sp->state = FULL;
+ sp->opacity = 1;
+
+ /* Just after reaching full opacity, pulse the width up and down. */
+ if (sp->fade_duration > 0 &&
+ secs < sp->pause_duration + sp->fade_duration * 2)
+ {
+ GLfloat f = ((secs - (sp->pause_duration + sp->fade_duration)) /
+ sp->fade_duration);
+ if (sp->throb_p)
+ sp->thickness_scale = 1 + 3 * (f > 0.5 ? 1-f : f);
+ }
+ }
+ else if (secs < total)
+ {
+ sp->state = OUT;
+ sp->opacity = (total - secs) / sp->fade_duration;
+ }
+ else
+ {
+ sp->state = DEAD;
+ sp->opacity = 0;
+ }
+
+ sp->frame_count++;
+}
+
+
+/* Draw the given sprite at the phase of its animation dictated by
+ its creation time compared to the current wall clock.
+ */
+static void
+draw_image_sprite (ModeInfo *mi, sprite *sp)
+{
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+ int wire = MI_IS_WIREFRAME(mi);
+ image *img = sp->img;
+
+ if (! sp->img) abort();
+ if (! img->loaded_p) abort();
+
+ glPushMatrix();
+ {
+ GLfloat s = 1 + (sp->thickness_scale - 1) / 40.0;
+ glTranslatef (0.5, 0.5, 0);
+ glScalef (s, s, 1);
+ glTranslatef (-0.5, -0.5, 0);
+
+ glTranslatef (sp->current.x, sp->current.y, 0);
+ glScalef (sp->current.w, sp->current.h, 1);
+
+ glTranslatef (-0.5, -0.5, 0);
+
+ if (wire) /* Draw a grid inside the box */
+ {
+ GLfloat dy = 0.1;
+ GLfloat dx = dy * img->w / img->h;
+ GLfloat x, y;
+
+ if (sp->id & 1)
+ glColor4f (sp->opacity, 0, 0, 1);
+ else
+ glColor4f (0, 0, sp->opacity, 1);
+
+ glBegin(GL_LINES);
+ glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
+
+ for (y = 0; y < 1+dy; y += dy)
+ {
+ GLfloat yy = (y > 1 ? 1 : y);
+ for (x = 0.5; x < 1+dx; x += dx)
+ {
+ GLfloat xx = (x > 1 ? 1 : x);
+ glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
+ glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
+ }
+ for (x = 0.5; x > -dx; x -= dx)
+ {
+ GLfloat xx = (x < 0 ? 0 : x);
+ glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
+ glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
+ }
+ }
+ glEnd();
+ }
+ else /* Draw the texture quad */
+ {
+ GLfloat texw = img->geom.width / (GLfloat) img->tw;
+ GLfloat texh = img->geom.height / (GLfloat) img->th;
+ GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
+ GLfloat texy1 = img->geom.y / (GLfloat) img->th;
+ GLfloat texx2 = texx1 + texw;
+ GLfloat texy2 = texy1 + texh;
+ GLfloat o = sp->opacity;
+ GLint mag = (sp->fatbits_p ? GL_NEAREST : GL_LINEAR);
+
+ glBindTexture (GL_TEXTURE_2D, img->texid);
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mag);
+
+ /* o = 1 - sin ((1 - o*o*o) * M_PI/2); */
+ glColor4f (1, 1, 1, o);
+
+ glNormal3f (0, 0, 1);
+ glBegin (GL_QUADS);
+ glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
+ glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
+ glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
+ glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
+ glEnd();
+
+ if (debug_p) /* Draw a border around the image */
+ {
+ if (!wire) glDisable (GL_TEXTURE_2D);
+ glColor4f (sp->opacity, 0, 0, 1);
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (0, 0, 0);
+ glVertex3f (0, 1, 0);
+ glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0);
+ glEnd();
+ if (!wire) glEnable (GL_TEXTURE_2D);
+ }
+ }
+ }
+ glPopMatrix();
+}
+
+
+static void
+draw_line_sprite (ModeInfo *mi, sprite *sp)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int w = MI_WIDTH(mi);
+ int h = MI_HEIGHT(mi);
+ int wh = (w > h ? w : h);
+ int gs = (sp->type == RETICLE ? grid_size+1 : grid_size);
+ int sx = wh / (gs + 1);
+ int sy;
+ int k;
+ GLfloat t = grid_thickness * sp->thickness_scale;
+ int fade;
+ GLfloat color[4];
+
+ GLfloat x = w * sp->current.x;
+ GLfloat y = h * sp->current.y;
+ GLfloat bw = w * sp->current.w;
+ GLfloat bh = h * sp->current.h;
+
+ if (MI_WIDTH(mi) > 2560) t *= 3; /* Retina displays */
+
+ if (sx < 10) sx = 10;
+ sy = sx;
+
+ if (t > sx/3) t = sx/3;
+ if (t < 1) t = 1;
+ fade = t;
+ if (fade < 1) fade = 1;
+
+ if (t <= 0 || sp->opacity <= 0) return;
+
+ glPushMatrix();
+ glLoadIdentity();
+
+ if (debug_p)
+ {
+ GLfloat s = 0.75;
+ glScalef (s, s, s);
+ }
+
+ glOrtho (0, w, 0, h, -1, 1);
+
+ switch (sp->type) {
+ case GRID: memcpy (color, ss->grid_color, sizeof(color)); break;
+ case RETICLE: memcpy (color, ss->reticle_color, sizeof(color)); break;
+ case BOX: memcpy (color, ss->reticle_color, sizeof(color)); break;
+ default: abort();
+ }
+
+ if (sp->type == GRID)
+ {
+ GLfloat s = 1 + (sp->thickness_scale - 1) / 120.0;
+ glTranslatef (w/2, h/2, 0);
+ glScalef (s, s, 1);
+ glTranslatef (-w/2, -h/2, 0);
+ }
+
+ glColor4fv (color);
+
+ if (!wire) glDisable (GL_TEXTURE_2D);
+
+ for (k = 0; k < fade; k++)
+ {
+ GLfloat t2 = t * (1 - (k / (fade * 1.0)));
+ if (t2 <= 0) break;
+ color[3] = sp->opacity / fade;
+ glColor4fv (color);
+
+ glBegin (wire ? GL_LINES : GL_QUADS);
+
+ switch (sp->type) {
+ case GRID:
+ {
+ GLfloat xoff = (w - sx * (w / sx)) / 2.0;
+ GLfloat yoff = (h - sy * (h / sy)) / 2.0;
+ for (y = -sy/2+t2/2; y < h; y += sy)
+ for (x = -sx/2-t2/2; x < w; x += sx)
+ {
+ glVertex3f (xoff+x+t2, yoff+y, 0);
+ glVertex3f (xoff+x+t2, yoff+y+sy-t2, 0);
+ glVertex3f (xoff+x, yoff+y+sy-t2, 0);
+ glVertex3f (xoff+x, yoff+y, 0);
+ mi->polygon_count++;
+
+ glVertex3f (xoff+x, yoff+y-t2, 0);
+ glVertex3f (xoff+x+sx, yoff+y-t2, 0);
+ glVertex3f (xoff+x+sx, yoff+y, 0);
+ glVertex3f (xoff+x, yoff+y, 0);
+ mi->polygon_count++;
+ }
+ }
+ break;
+
+ case BOX:
+ glVertex3f (x-bw/2-t2/2, y-bh/2-t2/2, 0);
+ glVertex3f (x+bw/2+t2/2, y-bh/2-t2/2, 0);
+ glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
+ glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x+bw/2+t2/2, y+bh/2+t2/2, 0);
+ glVertex3f (x-bw/2-t2/2, y+bh/2+t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x-bw/2+t2/2, y-bh/2+t2/2, 0);
+ glVertex3f (x-bw/2+t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x-bw/2-t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x-bw/2-t2/2, y-bh/2+t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x+bw/2+t2/2, y-bh/2+t2/2, 0);
+ glVertex3f (x+bw/2+t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x+bw/2-t2/2, y+bh/2-t2/2, 0);
+ glVertex3f (x+bw/2-t2/2, y-bh/2+t2/2, 0);
+ mi->polygon_count++;
+ break;
+
+ case RETICLE:
+ glVertex3f (x+t2/2, y+sy/2-t2/2, 0);
+ glVertex3f (x+t2/2, h, 0);
+ glVertex3f (x-t2/2, h, 0);
+ glVertex3f (x-t2/2, y+sy/2-t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x-t2/2, y-sy/2+t2/2, 0);
+ glVertex3f (x-t2/2, 0, 0);
+ glVertex3f (x+t2/2, 0, 0);
+ glVertex3f (x+t2/2, y-sy/2+t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x-sx/2+t2/2, y+t2/2, 0);
+ glVertex3f (0, y+t2/2, 0);
+ glVertex3f (0, y-t2/2, 0);
+ glVertex3f (x-sx/2+t2/2, y-t2/2, 0);
+ mi->polygon_count++;
+
+ glVertex3f (x+sx/2-t2/2, y-t2/2, 0);
+ glVertex3f (w, y-t2/2, 0);
+ glVertex3f (w, y+t2/2, 0);
+ glVertex3f (x+sx/2-t2/2, y+t2/2, 0);
+ mi->polygon_count++;
+ break;
+
+ default: abort();
+ }
+ glEnd();
+ }
+
+ glPopMatrix();
+
+ if (!wire) glEnable (GL_TEXTURE_2D);
+}
+
+
+static sprite * find_newest_sprite (ModeInfo *, sprite_type);
+static void compute_image_rect (rect *, sprite *, Bool);
+
+static void
+draw_text_sprite (ModeInfo *mi, sprite *sp)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ GLfloat w = MI_WIDTH(mi);
+ GLfloat h = MI_HEIGHT(mi);
+ GLfloat s;
+ int x, y, z;
+ XCharStruct e;
+ sprite *target = 0;
+ char text[255];
+ GLfloat color[4];
+ int i;
+
+ if (sp->opacity <= 0)
+ return;
+
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp2 = ss->sprites[i];
+ if (sp2->id == sp->text_id && sp2->state != DEAD)
+ {
+ target = sp2;
+ break;
+ }
+ }
+
+ if (target)
+ {
+ rect r;
+ sprite *img;
+
+ if (target->opacity <= 0 &&
+ (target->state == NEW || target->state == IN))
+ return;
+
+ r = target->current;
+
+ img = find_newest_sprite (mi, IMAGE);
+ if (img)
+ compute_image_rect (&r, img, target->back_p);
+
+ mi->recursion_depth = (img
+ ? MIN (img->current.w, img->current.h)
+ : 0);
+
+ x = abs ((int) (r.x * 10000)) % 10000;
+ y = abs ((int) (r.y * 10000)) % 10000;
+ z = abs ((int) (r.w * 10000)) % 10000;
+
+ sprintf (text, "ZM %04d NS %04d EW %04d", z, y, x);
+
+ if ((x == 0 || x == 5000) && /* startup */
+ (y == 0 || y == 5000) &&
+ (z == 0 || z == 5000))
+ *text = 0;
+
+ if (do_titles &&
+ target->type == IMAGE &&
+ target->remain_p) /* The initial background image */
+ {
+ char *s = (target->img &&
+ target->img->title && *target->img->title
+ ? target->img->title
+ : "Loading");
+ int L = strlen (s);
+ int i = (L > 23 ? L-23 : 0);
+ sprintf (text, ">>%-23s", target->img->title + i);
+ for (s = text; *s; s++)
+ if (*s >= 'a' && *s <= 'z') *s += ('A'-'a');
+ else if (*s == '/' || *s == '-' || *s == '.') *s = '_';
+ }
+
+ if (!*text) return;
+
+ if (sp->text) free (sp->text);
+ sp->text = strdup (text);
+ }
+ else if (sp->text && *sp->text)
+ /* The target sprite might be dead, but we saved our last text. */
+ strcpy (text, sp->text);
+ else
+ /* No target, no saved text. */
+ return;
+
+ texture_string_metrics (ss->font_data, text, &e, 0, 0);
+
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho (0, 1, 0, 1, -1, 1);
+
+ /* Scale the text to fit N characters horizontally. */
+ {
+# ifdef HAVE_MOBILE
+ GLfloat c = 25;
+# else /* desktop */
+ GLfloat c = (MI_WIDTH(mi) <= 640 ? 25 :
+ MI_WIDTH(mi) <= 1280 ? 32 : 64);
+# endif
+ s = w / (e.ascent * c);
+ }
+ w /= s;
+ h /= s;
+ x = (w - e.width) / 2;
+ y = e.ascent + e.descent * 2;
+
+ glScalef (1.0/w, 1.0/h, 1);
+ glTranslatef (x, y, 0);
+
+ memcpy (color, ss->text_color, sizeof(color));
+ color[3] = sp->opacity;
+ glColor4fv (color);
+
+ if (wire)
+ glEnable (GL_TEXTURE_2D);
+
+ print_texture_string (ss->font_data, text);
+ mi->polygon_count++;
+
+ if (wire)
+ glDisable (GL_TEXTURE_2D);
+ glPopMatrix();
+}
+
+
+static void
+draw_flash_sprite (ModeInfo *mi, sprite *sp)
+{
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+ GLfloat o = sp->opacity;
+
+ if (o <= 0) return;
+ o = 0.7; /* Too fast to see, so keep it consistent */
+
+ glPushMatrix();
+ int wire = MI_IS_WIREFRAME(mi);
+ if (!wire)
+ glDisable (GL_TEXTURE_2D);
+ glColor4f (0, 0, 1, o);
+ glColorMask (0, 0, 1, 1); /* write only into blue and alpha channels */
+ glBegin (GL_QUADS);
+ glVertex3f (0, 0, 0);
+ glVertex3f (1, 0, 0);
+ glVertex3f (1, 1, 0);
+ glVertex3f (0, 1, 0);
+ glEnd();
+ glColorMask (1, 1, 1, 1);
+ if (!wire)
+ glEnable (GL_TEXTURE_2D);
+ glPopMatrix();
+}
+
+
+static void
+draw_sprite (ModeInfo *mi, sprite *sp)
+{
+ switch (sp->type) {
+ case IMAGE:
+ draw_image_sprite (mi, sp);
+ break;
+ case RETICLE:
+ case BOX:
+ case GRID:
+ draw_line_sprite (mi, sp);
+ break;
+ case TEXT:
+ draw_text_sprite (mi, sp);
+ break;
+ case FLASH:
+ draw_flash_sprite (mi, sp);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+static void
+tick_sprites (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < ss->nsprites; i++)
+ tick_sprite (mi, ss->sprites[i]);
+
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp = ss->sprites[i];
+ if (sp->state == DEAD)
+ {
+ destroy_sprite (mi, sp);
+ i--;
+ }
+ }
+}
+
+
+static void
+draw_sprites (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
+
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix();
+
+/*
+ {
+ GLfloat rot = current_device_rotation();
+ glTranslatef (0.5, 0.5, 0);
+ glRotatef(rot, 0, 0, 1);
+ if ((rot > 45 && rot < 135) ||
+ (rot < -45 && rot > -135))
+ {
+ GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
+ glScalef (s, 1/s, 1);
+ }
+ glTranslatef (-0.5, -0.5, 0);
+ }
+*/
+
+ /* Draw the images first, then the overlays. */
+ for (i = 0; i < ss->nsprites; i++)
+ if (ss->sprites[i]->type == IMAGE)
+ draw_sprite (mi, ss->sprites[i]);
+ for (i = 0; i < ss->nsprites; i++)
+ if (ss->sprites[i]->type != IMAGE)
+ draw_sprite (mi, ss->sprites[i]);
+
+ glPopMatrix();
+
+ if (debug_p) /* draw a white box (the "screen") */
+ {
+ int wire = MI_IS_WIREFRAME(mi);
+
+ if (!wire) glDisable (GL_TEXTURE_2D);
+
+ glColor4f (1, 1, 1, 1);
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (0, 0, 0);
+ glVertex3f (0, 1, 0);
+ glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0);
+ glEnd();
+
+ if (!wire) glEnable (GL_TEXTURE_2D);
+ }
+}
+
+
+static void
+fadeout_sprite (ModeInfo *mi, sprite *sp)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+
+ /* If it hasn't faded in yet, don't fade out. */
+ if (ss->now <= sp->start_time + sp->pause_duration)
+ sp->fade_duration = 0;
+
+ /* Pretend it's at the point where it should fade out. */
+ sp->pause_duration = 0;
+ sp->duration = 9999;
+ sp->remain_p = False;
+ sp->start_time = ss->now - sp->duration;
+}
+
+static void
+fadeout_sprites (ModeInfo *mi, sprite_type type)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp = ss->sprites[i];
+ if (sp->type == type)
+ fadeout_sprite (mi, sp);
+ }
+}
+
+
+static sprite *
+find_newest_sprite (ModeInfo *mi, sprite_type type)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ sprite *sp = 0;
+ int i;
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp2 = ss->sprites[i];
+ if (sp2->type == type &&
+ (!sp ||
+ (sp->start_time < sp2->start_time &&
+ ss->now >= sp2->start_time + sp2->pause_duration)))
+ sp = sp2;
+ }
+ return sp;
+}
+
+
+/* Enqueue a text sprite describing the given sprite that runs at the
+ same time.
+ */
+static sprite *
+push_text_sprite (ModeInfo *mi, sprite *sp)
+{
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+ sprite *sp2 = new_sprite (mi, TEXT);
+ if (!sp2) abort();
+ sp2->text_id = sp->id;
+ sp2->fade_duration = sp->fade_duration;
+ sp2->duration = sp->duration;
+ sp2->pause_duration = sp->pause_duration;
+ return sp2;
+}
+
+
+/* Enqueue a flash sprite that fires at the same time.
+ */
+#ifndef SMOOTH
+static sprite *
+push_flash_sprite (ModeInfo *mi, sprite *sp)
+{
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+ sprite *sp2 = new_sprite (mi, FLASH);
+ if (!sp2) abort();
+ if (sp->type != IMAGE) abort();
+ sp2->text_id = sp->id;
+ sp2->duration = MAX (0.07 / speed, 0.07);
+ sp2->fade_duration = 0; /* Fading these is too fast to see */
+ sp2->pause_duration = sp->pause_duration + (sp->fade_duration * 0.3);
+ return sp2;
+}
+#endif /* !SMOOTH */
+
+
+/* Set the sprite's duration based on distance travelled.
+ */
+static void
+compute_sprite_duration (ModeInfo *mi, sprite *sp, Bool blink_p)
+{
+ /* Compute max distance traveled by any point (corners or center). */
+ /* (cpp is the devil) */
+# define L(F) (sp->F.x - sp->F.w/2) /* delta of left edge, from/to */
+# define R(F) (1-(sp->F.x + sp->F.w/2)) /* right */
+# define B(F) (sp->F.y - sp->F.h/2) /* top */
+# define T(F) (1-(sp->F.y + sp->F.h/2)) /* bottom */
+# define D(F,G) sqrt(F(from)*F(from) + G(to)*G(to)) /* corner traveled */
+ double BL = D(B,L);
+ double BR = D(B,R);
+ double TL = D(T,L);
+ double TR = D(T,R);
+ double cx = sp->to.x - sp->from.x;
+ double cy = sp->to.y - sp->from.y;
+ double C = sqrt(cx*cx + cy*cy);
+ double dist = MAX (BL, MAX (BR, MAX (TL, MAX (TR, C))));
+# undef L
+# undef R
+# undef B
+# undef T
+# undef D
+
+ int steps = 1 + dist * 28;
+ if (steps > 10) steps = 10;
+
+ sp->duration = steps * 0.2 / speed;
+
+# ifndef SMOOTH
+ sp->duration += 1.5 / speed; /* For linger added by animate_sprite_path() */
+ if (blink_p) sp->duration += 0.6 / speed;
+# endif
+}
+
+
+/* Convert the sprite to a jerky transition.
+ Instead of smoothly animating, move in discrete steps,
+ using multiple staggered sprites.
+ */
+static void
+animate_sprite_path (ModeInfo *mi, sprite *sp, Bool blink_p)
+{
+# ifndef SMOOTH
+ /* esper_state *ss = &sss[MI_SCREEN(mi)]; */
+ double dx = sp->to.x - sp->from.x;
+ double dy = sp->to.y - sp->from.y;
+ double dw = sp->to.w - sp->from.w;
+ double dh = sp->to.h - sp->from.h;
+ double linger = 1.5 / speed;
+ double blinger = 0.6 / speed;
+ double dur = sp->duration - linger - (blink_p ? blinger : 0);
+ int steps = dur / 0.3 * speed; /* step duration in seconds */
+ int i;
+
+ if (sp->type == IMAGE)
+ steps *= 0.8;
+
+ if (steps < 2) steps = 2;
+ if (steps > 10) steps = 10;
+
+ /* if (dur <= 0.01) abort(); */
+ if (dur < 0.01)
+ linger = blinger = 0;
+
+ for (i = 0; i <= steps; i++)
+ {
+ sprite *sp2 = copy_sprite (mi, sp);
+ if (!sp2) abort();
+
+ sp2->to.x = (sp->current.x + i * dx / steps);
+ sp2->to.y = (sp->current.y + i * dy / steps);
+ sp2->to.w = (sp->current.w + i * dw / steps);
+ sp2->to.h = (sp->current.h + i * dh / steps);
+ sp2->current = sp2->from = sp2->to;
+ sp2->duration = dur / steps;
+ sp2->pause_duration += i * sp2->duration;
+ sp2->remain_p = False;
+ sp2->fatbits_p = True;
+
+ if (i == steps)
+ sp2->duration += linger; /* last one lingers for a bit */
+
+ if (i == steps && !blink_p)
+ {
+ sp2->remain_p = sp->remain_p;
+ sp2->fatbits_p = False;
+ }
+
+ if (sp2->type == IMAGE && i > 0)
+ push_flash_sprite (mi, sp2);
+
+ if (sp2->type == RETICLE || sp2->type == BOX)
+ {
+ sp2 = push_text_sprite (mi, sp2);
+ if (i == steps)
+ sp2->duration += linger * 2;
+ }
+ }
+
+ if (blink_p && blinger) /* last one blinks before vanishing */
+ {
+ int blinkers = 3;
+ for (i = 1; i <= blinkers; i++)
+ {
+ sprite *sp2 = copy_sprite (mi, sp);
+ if (!sp2) abort();
+
+ sp2->current = sp2->from = sp->to;
+ sp2->duration = blinger / blinkers;
+ sp2->pause_duration += dur + linger + i * sp2->duration;
+ sp2->remain_p = False;
+ if (i == blinkers)
+ {
+ sp2->remain_p = sp->remain_p;
+ sp2->fatbits_p = False;
+ }
+ }
+ }
+
+ /* Fade out the template sprite. It might not have even appeared yet. */
+ fadeout_sprite (mi, sp);
+# endif
+}
+
+
+/* Input rect is of a reticle or box.
+ Output rect is what the image's rect should be so that the only part
+ visible is the part indicated by the input rect.
+ */
+static void
+compute_image_rect (rect *r, sprite *img, Bool inverse_p)
+{
+ double scale = (inverse_p ? 1/r->w : r->w);
+ double dx = r->x - 0.5;
+ double dy = r->y - 0.5;
+
+ /* Adjust size and center by zoom factor */
+ r->w = img->current.w / scale;
+ r->h = img->current.h / scale;
+ r->x = 0.5 + (img->current.x - 0.5) / scale;
+ r->y = 0.5 + (img->current.y - 0.5) / scale;
+
+ /* Move center */
+
+ if (inverse_p)
+ {
+ dx = -dx; /* #### Close but not quite right */
+ dy = -dy;
+ }
+
+ r->x -= dx / scale;
+ r->y -= dy / scale;
+}
+
+
+/* Sets 'to' such that the image zooms out so that the only part visible
+ is the part indicated by the box.
+ */
+static void
+track_box_with_image (ModeInfo *mi, sprite *sp, sprite *img)
+{
+ rect r = sp->current;
+ compute_image_rect (&r, img, sp->back_p);
+ img->to = r;
+
+ /* Never zoom out too far. */
+ if (img->to.w < 1 && img->to.h < 1)
+ {
+ if (img->to.w > img->to.h)
+ {
+ img->to.w = img->to.w / img->to.h;
+ img->to.h = 1;
+ }
+ else
+ {
+ img->to.h = img->to.h / img->to.w;
+ img->to.w = 1;
+ }
+ }
+
+ /* Never pan beyond the bounds of the image. */
+ if (img->to.x < -img->to.w/2+1) img->to.x = -img->to.w/2+1;
+ if (img->to.x > img->to.w/2) img->to.x = img->to.w/2;
+ if (img->to.y < -img->to.h/2+1) img->to.y = -img->to.h/2+1;
+ if (img->to.y > img->to.h/2) img->to.y = img->to.h/2;
+}
+
+
+static void
+tick_animation (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ anim_state prev_state = ss->anim_state;
+ sprite *sp = 0;
+ int i;
+
+ switch (ss->anim_state) {
+ case BLANK:
+ ss->anim_state = GRID_ON;
+ break;
+ case GRID_ON:
+ ss->anim_state = IMAGE_LOAD;
+ break;
+ case IMAGE_LOAD:
+ /* Only advance once an image has loaded. */
+ if (find_newest_sprite (mi, IMAGE))
+ ss->anim_state = RETICLE_ON;
+ else
+ ss->anim_state = IMAGE_LOAD;
+ break;
+ case RETICLE_ON:
+ ss->anim_state = RETICLE_MOVE;
+ break;
+ case RETICLE_MOVE:
+ if (random() % 6)
+ ss->anim_state = BOX_MOVE;
+ else
+ ss->anim_state = IMAGE_ZOOM;
+ break;
+ case BOX_MOVE:
+ ss->anim_state = IMAGE_ZOOM;
+ break;
+ case IMAGE_ZOOM:
+ {
+ sprite *sp = find_newest_sprite (mi, IMAGE);
+ double depth = (sp
+ ? MIN (sp->current.w, sp->current.h)
+ : 0);
+ if (depth > 20)
+ ss->anim_state = IMAGE_UNLOAD;
+ else
+ ss->anim_state = RETICLE_ON;
+ }
+ break;
+ case IMAGE_FORCE_UNLOAD:
+ ss->anim_state = IMAGE_UNLOAD;
+ break;
+ case IMAGE_UNLOAD:
+ ss->anim_state = IMAGE_LOAD;
+ break;
+ case MANUAL_BOX_ON:
+ ss->anim_state = MANUAL_BOX;
+ break;
+ case MANUAL_BOX:
+ break;
+ case MANUAL_RETICLE_ON:
+ ss->anim_state = MANUAL_RETICLE;
+ break;
+ case MANUAL_RETICLE:
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ ss->anim_start = ss->now;
+ ss->anim_duration = 0;
+
+ if (debug_p)
+ fprintf (stderr, "%s: entering %s\n",
+ progname, state_name (ss->anim_state));
+
+ switch (ss->anim_state) {
+
+ case GRID_ON: /* Start the grid fading in. */
+ if (! find_newest_sprite (mi, GRID))
+ {
+ sp = new_sprite (mi, GRID);
+ if (!sp) abort();
+ sp->fade_duration = 1.0 / speed;
+ sp->duration = 2.0 / speed;
+ sp->remain_p = True;
+ ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
+ sp->duration);
+ }
+ break;
+
+ case IMAGE_LOAD:
+ fadeout_sprites (mi, IMAGE);
+ sp = new_sprite (mi, IMAGE);
+ if (! sp)
+ {
+ if (debug_p) fprintf (stderr, "%s: image load failed\n", progname);
+ break;
+ }
+
+ sp->fade_duration = 0.5 / speed;
+ sp->duration = sp->fade_duration * 3;
+ sp->remain_p = True;
+ /* If we zoom in, we lose the pulse at the end. */
+ /* sp->from.w = sp->from.h = 0.0001; */
+ sp->current = sp->from;
+
+ ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
+ sp->duration);
+
+ sp = push_text_sprite (mi, sp);
+ sp->fade_duration = 0.2 / speed;
+ sp->pause_duration = 0;
+ sp->duration = 2.5 / speed;
+ break;
+
+ case IMAGE_FORCE_UNLOAD:
+ break;
+
+ case IMAGE_UNLOAD:
+ sp = find_newest_sprite (mi, IMAGE);
+ if (sp)
+ sp->fade_duration = ((prev_state == IMAGE_FORCE_UNLOAD ? 0.2 : 3.0)
+ / speed);
+ fadeout_sprites (mi, IMAGE);
+ fadeout_sprites (mi, RETICLE);
+ fadeout_sprites (mi, BOX);
+ fadeout_sprites (mi, TEXT);
+ ss->anim_duration = (sp ? sp->fade_duration : 0) + 3.5 / speed;
+ break;
+
+ case RETICLE_ON: /* Display reticle at center. */
+ fadeout_sprites (mi, TEXT);
+ sp = new_sprite (mi, RETICLE);
+ if (!sp) abort();
+ sp->fade_duration = 0.2 / speed;
+ sp->pause_duration = 1.0 / speed;
+ sp->duration = 1.5 / speed;
+ ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
+ sp->duration);
+ ss->anim_duration -= sp->fade_duration * 2;
+ break;
+
+ case RETICLE_MOVE:
+ /* Reticle has faded in. Now move it to somewhere else.
+ Create N new reticle sprites, wih staggered pause_durations.
+ */
+ {
+ GLfloat ox = 0.5;
+ GLfloat oy = 0.5;
+ GLfloat nx, ny, dist;
+
+ do { /* pick a new position not too near the old */
+ nx = 0.3 + BELLRAND(0.4);
+ ny = 0.3 + BELLRAND(0.4);
+ dist = sqrt ((nx-ox)*(nx-ox) + (ny-oy)*(ny-oy));
+ } while (dist < 0.1);
+
+ sp = new_sprite (mi, RETICLE);
+ if (!sp) abort();
+
+ sp->from.x = ox;
+ sp->from.y = oy;
+ sp->current = sp->to = sp->from;
+ sp->to.x = nx;
+ sp->to.y = ny;
+ sp->fade_duration = 0.2 / speed;
+ sp->pause_duration = 0;
+ compute_sprite_duration (mi, sp, False);
+
+ ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
+ sp->duration - 0.1);
+ animate_sprite_path (mi, sp, False);
+ }
+ break;
+
+ case BOX_MOVE:
+ /* Reticle has moved, and faded out.
+ Start the box zooming into place.
+ */
+ {
+ GLfloat ox = 0.5;
+ GLfloat oy = 0.5;
+ GLfloat nx, ny;
+ GLfloat z;
+
+ /* Find the last-added reticle, for our destination position. */
+ sp = 0;
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp2 = ss->sprites[i];
+ if (sp2->type == RETICLE &&
+ (!sp || sp->start_time < sp2->start_time))
+ sp = sp2;
+ }
+ if (sp)
+ {
+ nx = sp->to.x;
+ ny = sp->to.y;
+ }
+ else
+ {
+ nx = ny = 0.5;
+ if (debug_p)
+ fprintf (stderr, "%s: no reticle before box?\n", progname);
+ }
+
+ z = 0.3 + frand(0.5);
+
+ /* Ensure that the selected box is contained within the screen */
+ {
+ double margin = 0.005;
+ double maxw = 2 * MIN (1 - margin - nx, nx - margin);
+ double maxh = 2 * MIN (1 - margin - ny, ny - margin);
+ double max = MIN (maxw, maxh);
+ if (z > max) z = max;
+ }
+
+ sp = new_sprite (mi, BOX);
+ if (!sp) abort();
+ sp->from.x = ox;
+ sp->from.y = oy;
+ sp->from.w = 1.0;
+ sp->from.h = 1.0;
+ sp->current = sp->from;
+ sp->to.x = nx;
+ sp->to.y = ny;
+ sp->to.w = z;
+ sp->to.h = z;
+
+ /* Maybe zoom out instead of in.
+ */
+ {
+ sprite *img = find_newest_sprite (mi, IMAGE);
+ double depth = MIN (img->current.w, img->current.h);
+ if (depth > 1 && /* if zoomed in */
+ (depth < 6 ? !(random() % 5) : /* 20% */
+ depth < 12 ? !(random() % 2) : /* 50% */
+ (random() % 3))) /* 66% */
+ {
+ sp->back_p = True;
+ if (depth < 1.5 && z < 0.8)
+ {
+ z = 0.8; /* don't zoom out much past 100% */
+ sp->to.w = z;
+ sp->to.h = z;
+ }
+ }
+ }
+
+ sp->fade_duration = 0.2 / speed;
+ sp->pause_duration = 2.0 / speed;
+ compute_sprite_duration (mi, sp, True);
+ ss->anim_duration = (sp->pause_duration + sp->fade_duration * 2 +
+ sp->duration - 0.1);
+ animate_sprite_path (mi, sp, True);
+ }
+ break;
+
+ case IMAGE_ZOOM:
+
+ /* Box has moved, and faded out.
+ Or, if no box, then just a reticle.
+ Zoom the underlying image to track the box's position. */
+ {
+ sprite *img, *img2;
+
+ /* Find latest box or reticle, for our destination position. */
+ sp = find_newest_sprite (mi, BOX);
+ if (! sp)
+ sp = find_newest_sprite (mi, RETICLE);
+ if (! sp)
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: no box or reticle before image\n",
+ progname);
+ break;
+ }
+
+ img = find_newest_sprite (mi, IMAGE);
+ if (!img)
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: no image?\n", progname);
+ break;
+ }
+
+ img2 = copy_sprite (mi, img);
+ if (!img2) abort();
+
+ img2->from = img->current;
+
+ fadeout_sprite (mi, img);
+
+ track_box_with_image (mi, sp, img2);
+
+ img2->fade_duration = 0.2 / speed;
+ img2->pause_duration = 0.5 / speed;
+ img2->remain_p = True;
+ img2->throb_p = False;
+ compute_sprite_duration (mi, img2, False);
+
+ img->start_time += img2->pause_duration;
+
+ ss->anim_duration = (img2->pause_duration + img2->fade_duration * 2 +
+ img2->duration);
+ animate_sprite_path (mi, img2, False);
+ fadeout_sprites (mi, TEXT);
+ }
+ break;
+
+ case MANUAL_BOX_ON:
+ case MANUAL_RETICLE_ON:
+ break;
+
+ case MANUAL_BOX:
+ case MANUAL_RETICLE:
+ {
+ sprite_type tt = (ss->anim_state == MANUAL_BOX ? BOX : RETICLE);
+ sprite *osp = find_newest_sprite (mi, tt);
+ fadeout_sprites (mi, RETICLE);
+ fadeout_sprites (mi, BOX);
+
+ sp = new_sprite (mi, tt);
+ if (!sp) abort();
+ if (osp)
+ sp->from = osp->current;
+ else
+ {
+ sp->from.x = 0.5;
+ sp->from.y = 0.5;
+ sp->from.w = 0.5;
+ sp->from.h = 0.5;
+ }
+ sp->to = sp->current = sp->from;
+ sp->fade_duration = 0.2 / speed;
+ sp->duration = 0.2 / speed;
+ sp->remain_p = True;
+ sp->throb_p = False;
+ ss->anim_duration = 9999;
+ }
+ break;
+
+ default:
+ fprintf (stderr,"%s: unknown state %d\n",
+ progname, (int) ss->anim_state);
+ abort();
+ }
+}
+
+
+/* Copied from gltrackball.c, sigh.
+ */
+static void
+adjust_for_device_rotation (double *x, double *y, double *w, double *h)
+{
+ int rot = (int) current_device_rotation();
+ int swap;
+
+ while (rot <= -180) rot += 360;
+ while (rot > 180) rot -= 360;
+
+ if (rot > 135 || rot < -135) /* 180 */
+ {
+ *x = *w - *x;
+ *y = *h - *y;
+ }
+ else if (rot > 45) /* 90 */
+ {
+ swap = *x; *x = *y; *y = swap;
+ swap = *w; *w = *h; *h = swap;
+ *x = *w - *x;
+ }
+ else if (rot < -45) /* 270 */
+ {
+ swap = *x; *x = *y; *y = swap;
+ swap = *w; *w = *h; *h = swap;
+ *y = *h - *y;
+ }
+}
+
+
+ENTRYPOINT Bool
+esper_handle_event (ModeInfo *mi, XEvent *event)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+
+ if (event->xany.type == Expose ||
+ event->xany.type == GraphicsExpose ||
+ event->xany.type == VisibilityNotify)
+ {
+ return False;
+ }
+ else if (event->xany.type == KeyPress)
+ {
+ KeySym keysym;
+ char c = 0;
+ sprite *sp = 0;
+ double delta = 0.025;
+ double margin = 0.005;
+ Bool ok = False;
+
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+
+ if (c == '\t')
+ {
+ ss->anim_state = IMAGE_FORCE_UNLOAD;
+ return True;
+ }
+
+ if (! find_newest_sprite (mi, IMAGE))
+ return False; /* Too early */
+
+ ss->now = double_time();
+
+# define BONK() do { \
+ if (ss->anim_state != MANUAL_BOX_ON && \
+ ss->anim_state != MANUAL_BOX) { \
+ ss->anim_state = MANUAL_BOX_ON; \
+ tick_animation (mi); \
+ } \
+ sp = find_newest_sprite (mi, BOX); \
+ if (!sp) abort() ; \
+ sp->from = sp->current; \
+ sp->to = sp->from; \
+ sp->start_time = ss->now - sp->fade_duration; \
+ ok = True; \
+ } while(0)
+
+ if (keysym == XK_Left || c == ',' || c == '<')
+ {
+ BONK();
+ sp->to.x -= delta;
+ }
+ else if (keysym == XK_Right || c == '.' || c == '>')
+ {
+ BONK();
+ sp->to.x += delta;
+ }
+ else if (keysym == XK_Down || c == '-')
+ {
+ BONK();
+ sp->to.y -= delta;
+ }
+ else if (keysym == XK_Up || c == '=')
+ {
+ BONK();
+ sp->to.y += delta, ok = True;
+ }
+ else if (keysym == XK_Prior || c == '+')
+ {
+ BONK();
+ sp->to.w += delta;
+ sp->to.h = sp->to.w * sp->from.w / sp->from.h;
+ }
+ else if (keysym == XK_Next || c == '_')
+ {
+ BONK();
+ sp->to.w -= delta;
+ sp->to.h = sp->to.w * sp->from.w / sp->from.h;
+ }
+ else if ((c == ' ' || c == '\t') && debug_p &&
+ ss->anim_state == MANUAL_BOX)
+ {
+ BONK(); /* Null motion: just flash the current image. */
+ }
+ else if ((keysym == XK_Home || c == ' ' || c == '\t') &&
+ ss->anim_state == MANUAL_BOX)
+ {
+ BONK();
+ sp->to.x = 0.5;
+ sp->to.y = 0.5;
+ sp->to.w = 0.5;
+ sp->to.h = 0.5;
+ }
+ else if ((c == '\r' || c == '\n' || c == 033) &&
+ ss->anim_state == MANUAL_BOX)
+ {
+ BONK();
+ ss->anim_state = BOX_MOVE;
+ ss->anim_duration = 9999;
+ ss->anim_start = ss->now - ss->anim_duration;
+ fadeout_sprite (mi, sp);
+ return True;
+ }
+ else
+ return False;
+
+ if (! ok)
+ return False;
+
+ /* Keep it on screen */
+ if (sp->to.w > 1 - margin)
+ {
+ GLfloat r = sp->to.h / sp->to.w;
+ sp->to.w = 1-margin;
+ sp->to.h = (1-margin) * r;
+ }
+ if (sp->to.h > 1)
+ {
+ GLfloat r = sp->to.h / sp->to.w;
+ sp->to.w = (1-margin) / r;
+ sp->to.h = 1-margin;
+ }
+
+ if (sp->to.x - sp->to.w/2 < margin)
+ sp->to.x = sp->to.w/2 + margin;
+ if (sp->to.y - sp->to.h/2 < margin)
+ sp->to.y = sp->to.h/2 + margin;
+
+ if (sp->to.x + sp->to.w/2 >= 1 + margin)
+ sp->to.x = 1 - (sp->to.w/2 + margin);
+ if (sp->to.y + sp->to.h/2 >= 1 + margin)
+ sp->to.y = 1 - (sp->to.h/2 + margin);
+
+ /* Now let's give a momentary glimpse of what the image would do. */
+ if (debug_p)
+ {
+ sprite *img = 0;
+ int i;
+
+ /* Find the lingering image */
+ /* img = find__sprite (mi, IMAGE); */
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp2 = ss->sprites[i];
+ if (sp2->type == IMAGE &&
+ sp2->remain_p &&
+ (!img ||
+ (img->start_time < sp2->start_time &&
+ ss->now >= sp2->start_time + sp2->pause_duration)))
+ img = sp2;
+ }
+
+ if (!img) abort();
+ img = copy_sprite (mi, img);
+ img->pause_duration = 0;
+ img->fade_duration = 0.1 / speed;
+ img->duration = 0.5 / speed;
+ img->start_time = ss->now;
+ img->remain_p = False;
+ track_box_with_image (mi, sp, img);
+ img->from = img->current = img->to;
+ }
+
+ return True;
+ }
+ else if (event->xany.type == ButtonPress &&
+ event->xbutton.button == Button1)
+ {
+ ss->button_down_p = 1;
+ return True;
+ }
+ else if (event->xany.type == ButtonRelease &&
+ event->xbutton.button == Button1)
+ {
+ ss->button_down_p = 0;
+
+ if (ss->anim_state == MANUAL_BOX)
+ {
+ sprite *sp = find_newest_sprite (mi, BOX);
+ if (sp) fadeout_sprite (mi, sp);
+ ss->anim_state = BOX_MOVE;
+ ss->anim_duration = 9999;
+ ss->anim_start = ss->now - ss->anim_duration;
+ }
+ else if (ss->anim_state == MANUAL_RETICLE)
+ {
+ sprite *sp = find_newest_sprite (mi, RETICLE);
+ if (sp) fadeout_sprite (mi, sp);
+ ss->anim_state = RETICLE_MOVE;
+ ss->anim_duration = 9999;
+ ss->anim_start = ss->now - ss->anim_duration;
+ }
+ return True;
+ }
+ else if (event->xany.type == MotionNotify &&
+ ss->button_down_p &&
+ (ss->anim_state == MANUAL_RETICLE ||
+ ss->anim_state == RETICLE_MOVE))
+ {
+ sprite *sp = 0;
+ double x = event->xmotion.x;
+ double y = event->xmotion.y;
+ double w = MI_WIDTH(mi);
+ double h = MI_HEIGHT(mi);
+
+ adjust_for_device_rotation (&x, &y, &w, &h);
+ x = x/w;
+ y = 1-y/h;
+
+ if (ss->anim_state != MANUAL_RETICLE_ON &&
+ ss->anim_state != MANUAL_RETICLE)
+ {
+ ss->anim_state = MANUAL_RETICLE_ON;
+ tick_animation (mi);
+ }
+ sp = find_newest_sprite (mi, RETICLE);
+ if (!sp) abort();
+ sp->from = sp->current;
+ sp->to = sp->from;
+ sp->start_time = ss->now - sp->fade_duration;
+ sp->remain_p = True;
+
+ sp->current.x = MIN (0.95, MAX (0.05, x));
+ sp->current.y = MIN (0.95, MAX (0.05, y));
+ sp->from = sp->to = sp->current;
+
+ /* Don't update the text sprite more often than once a second. */
+ {
+ sprite *sp2 = find_newest_sprite (mi, TEXT);
+ if (!sp2 || sp2->start_time < ss->now-1)
+ {
+ fadeout_sprites (mi, TEXT);
+ sp = push_text_sprite (mi, sp);
+ sp->remain_p = True;
+ }
+ }
+
+ return True;
+ }
+ else if (event->xany.type == MotionNotify &&
+ ss->button_down_p &&
+ (ss->anim_state == MANUAL_BOX ||
+ ss->anim_state == BOX_MOVE))
+ {
+ sprite *sp = 0;
+ double x = event->xmotion.x;
+ double y = event->xmotion.y;
+ double w = MI_WIDTH(mi);
+ double h = MI_HEIGHT(mi);
+ double max;
+ Bool ok = True;
+
+ adjust_for_device_rotation (&x, &y, &w, &h);
+ x = x/w;
+ y = 1-y/h;
+
+ BONK();
+ max = (2 * (0.5 - MAX (fabs (sp->current.x - 0.5),
+ fabs (sp->current.y - 0.5)))
+ * 0.95);
+
+ x = fabs (x - sp->current.x);
+ y = fabs (y - sp->current.y);
+
+ if (x > y)
+ sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*x));
+ else
+ sp->current.w = sp->current.h = MIN (max, MAX (0.05, 2*y));
+ sp->from = sp->to = sp->current;
+
+ /* Don't update the text sprite more often than once a second. */
+ {
+ sprite *sp2 = find_newest_sprite (mi, TEXT);
+ if (!sp2 || sp2->start_time < ss->now-1)
+ {
+ fadeout_sprites (mi, TEXT);
+ sp = push_text_sprite (mi, sp);
+ sp->remain_p = True;
+ }
+ }
+
+ return ok;
+ }
+ else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
+ {
+ ss->anim_state = IMAGE_FORCE_UNLOAD;
+ return True;
+ }
+# undef BONK
+
+ return False;
+}
+
+
+ENTRYPOINT void
+reshape_esper (ModeInfo *mi, int width, int height)
+{
+ GLfloat s;
+
+ glViewport (0, 0, width, height);
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ glRotatef (current_device_rotation(), 0, 0, 1);
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity();
+
+ s = 2;
+
+ if (debug_p)
+ {
+ s *= 0.75;
+ if (s < 0.1) s = 0.1;
+ }
+
+ glScalef (s, s, s);
+ glTranslatef (-0.5, -0.5, 0);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ /* Stretch each existing image to match new window aspect. */
+ {
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp = ss->sprites[i];
+ if (sp && sp->type == IMAGE && sp->img && sp->img->loaded_p)
+ {
+ GLfloat sp_asp = sp->current.h / sp->current.w;
+ GLfloat img_asp = (sp->img->geom.height /
+ (GLfloat) sp->img->geom.width);
+ GLfloat new_win = (MI_WIDTH(mi) / (double) MI_HEIGHT(mi));
+ GLfloat old_win = sp_asp / img_asp;
+ GLfloat r = old_win / new_win;
+ if (img_asp > 1)
+ {
+ sp->from.h /= r;
+ sp->current.h /= r;
+ sp->to.h /= r;
+ }
+ else
+ {
+ sp->from.w *= r;
+ sp->current.w *= r;
+ sp->to.w *= r;
+ }
+ }
+ }
+ }
+}
+
+
+static void
+parse_color (ModeInfo *mi, char *key, GLfloat color[4])
+{
+ XColor xcolor;
+ char *string = get_string_resource (mi->dpy, key, "EsperColor");
+ if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
+ {
+ fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
+ key, string);
+ exit (1);
+ }
+
+ color[0] = xcolor.red / 65536.0;
+ color[1] = xcolor.green / 65536.0;
+ color[2] = xcolor.blue / 65536.0;
+ color[3] = 1;
+}
+
+
+ENTRYPOINT void
+init_esper (ModeInfo *mi)
+{
+ int screen = MI_SCREEN(mi);
+ esper_state *ss;
+ int wire = MI_IS_WIREFRAME(mi);
+
+ MI_INIT (mi, sss);
+ ss = &sss[screen];
+
+ if ((ss->glx_context = init_GL(mi)) != NULL) {
+ reshape_esper (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+ } else {
+ MI_CLEARWINDOW(mi);
+ }
+
+ parse_color (mi, "gridColor", ss->grid_color);
+ parse_color (mi, "reticleColor", ss->reticle_color);
+ parse_color (mi, "textColor", ss->text_color);
+
+ glDisable (GL_LIGHTING);
+ glDisable (GL_DEPTH_TEST);
+ glDepthMask (GL_FALSE);
+ glEnable (GL_CULL_FACE);
+ glCullFace (GL_BACK);
+
+ if (! wire)
+ {
+ glEnable (GL_TEXTURE_2D);
+ glShadeModel (GL_SMOOTH);
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ ss->font_data = load_texture_font (mi->dpy, "titleFont");
+
+ ss->now = double_time();
+ ss->dawn_of_time = ss->now;
+
+ alloc_image (mi);
+
+ ss->anim_state = BLANK;
+ ss->anim_start = 0;
+ ss->anim_duration = 0;
+}
+
+
+ENTRYPOINT void
+draw_esper (ModeInfo *mi)
+{
+ esper_state *ss = &sss[MI_SCREEN(mi)];
+
+ if (!ss->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
+
+ mi->polygon_count = 0;
+
+ ss->now = double_time();
+
+ tick_sprites (mi);
+ draw_sprites (mi);
+ if (ss->now >= ss->anim_start + ss->anim_duration)
+ tick_animation (mi);
+
+ if (mi->fps_p) do_fps (mi);
+
+ glFinish();
+ glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
+}
+
+XSCREENSAVER_MODULE ("Esper", esper)
+
+#endif /* USE_GL */