]> git.hungrycats.org Git - xscreensaver/blob - hacks/glx/peepers.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / hacks / glx / peepers.c
1 /* peepers, Copyright (c) 2018-2019 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Created: 14 Feb 2018, jwz.
12  *
13  * Floating eyeballs!
14  *
15  * Inspired by @PaintYourDragon's Adafruit Snake Eyes Raspberry Pi Bonnet
16  * https://learn.adafruit.com/animated-snake-eyes-bonnet-for-raspberry-pi/
17  * which is excellent.
18  */
19
20 #define DEFAULTS        "*delay:        30000       \n" \
21                         "*count:        0           \n" \
22                         "*showFPS:      False       \n" \
23                         "*wireframe:    False       \n" \
24
25 # define release_peepers 0
26
27 #define DEF_SPEED "1.0"
28 #define DEF_MODE  "random"
29
30 #undef BELLRAND
31 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
32 #undef RANDSIGN
33 #define RANDSIGN() ((random() & 1) ? 1 : -1)
34
35 #include "xlockmore.h"
36 #include "normals.h"
37 #include "rotator.h"
38 #include "gltrackball.h"
39 #include "ximage-loader.h"
40 #include <ctype.h>
41
42 #ifndef HAVE_JWXYZ
43 # include <X11/Xatom.h>
44 #endif
45
46 #include "images/gen/sclera_png.h"
47 #include "images/gen/iris_png.h"
48
49 #ifdef USE_GL /* whole file */
50
51 typedef struct { double a, o; } LL;     /* latitude + longitude */
52
53 typedef struct {
54   int idx;
55   GLfloat x, y, z;
56   GLfloat dx, dy, dz;
57   GLfloat ddx, ddy, ddz;
58   rotator *rot;
59   struct { GLfloat from, to, current, tick; } dilation;
60   enum { ROTATE, SPIN, TRACK } focus;
61   XYZ track;
62   GLfloat tilt, roll;
63   GLfloat scale;
64   GLfloat color[4];
65   int jaundice;
66 } floater;
67
68 typedef enum { RETINA, IRIS, SCLERA, LENS, TICK } component;
69
70 typedef struct {
71   GLXContext *glx_context;
72   trackball_state *trackball;
73
74   Bool button_down_p;
75   XYZ mouse, last_mouse, fake_mouse;
76   time_t last_mouse_time;
77   int mouse_dx, mouse_dy;
78
79   GLuint retina_list, sclera_list, lens_list, iris_list;
80   GLuint sclera_texture, iris_texture;
81   int eye_polys;
82
83   int nfloaters;
84   floater *floaters;
85   enum { BOUNCE, SCROLL_LEFT, SCROLL_RIGHT, XEYES, BEHOLDER } mode;
86
87 } peepers_configuration;
88
89 static peepers_configuration *bps = NULL;
90
91 static GLfloat speed;
92 const char *mode_opt;
93
94 static XrmOptionDescRec opts[] = {
95   { "-speed", ".speed", XrmoptionSepArg, 0 },
96   { "-mode",  ".mode",  XrmoptionSepArg, 0 },
97 };
98
99 static argtype vars[] = {
100   {&speed,    "speed", "Speed", DEF_SPEED, t_Float},
101   {&mode_opt, "mode",  "Mode",  DEF_MODE,  t_String},
102 };
103
104 ENTRYPOINT ModeSpecOpt peepers_opts = {countof(opts), opts, countof(vars), vars, NULL};
105
106
107 /* Bottom edge of screen is -0.5; left and right scale by aspect. */
108 #define BOTTOM (-1.6)
109 #define LEFT   (BOTTOM * MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
110
111 static void
112 reset_floater (ModeInfo *mi, floater *f)
113 {
114   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
115   GLfloat r = ((bp->mode == BOUNCE ? LEFT : BOTTOM) *
116                (bp->nfloaters < 10 ? 0.3: 0.6));
117   GLfloat x, y;
118
119   if (bp->nfloaters <= 2)
120     {
121       x = frand(LEFT) * RANDSIGN() * 0.3;
122       y = 0;
123     }
124   else
125     {
126       /* Position them off screen in a circle */
127       GLfloat th = f->idx * (M_PI + (M_PI/6)) * 2 / bp->nfloaters;
128       x = r * cos (th);
129       y = r * sin (th) * 1.5;   /* Oval */
130     }
131
132   switch (bp->mode) {
133   case BOUNCE:
134     f->x = x;
135     f->y = BOTTOM;
136     f->z = y;
137
138     /* Yes, I know I'm varying the force of gravity instead of varying the
139        launch velocity.  That's intentional: empirical studies indicate
140        that it's way, way funnier that way. */
141
142     f->dy = 0.1;
143     f->dx = 0;
144     f->dz = 0;
145
146     {
147       GLfloat min = -0.004;
148       GLfloat max = -0.0019;
149       f->ddy = min + frand (max - min);
150       f->ddx = 0;
151       f->ddz = 0;
152     }
153
154     if (! (random() % (10 * bp->nfloaters)))
155       {
156         f->dx = BELLRAND(0.03) * RANDSIGN();
157         f->dz = BELLRAND(0.03) * RANDSIGN();
158       }
159     break;
160
161   case SCROLL_LEFT:
162   case SCROLL_RIGHT:
163
164     f->x = (bp->mode == SCROLL_LEFT ? -LEFT : LEFT);
165     f->y = x;
166     f->z = y;
167
168     f->dx = (1.0 + frand(2.0)) * 0.020 * (bp->mode == SCROLL_LEFT ? -1 : 1);
169     f->dy = (1.0 + frand(2.0)) * 0.002 * RANDSIGN();
170     f->dz = (1.0 + frand(2.0)) * 0.002 * RANDSIGN();
171     f->ddy = 0;
172     f->ddz = 0;
173     break;
174
175   case XEYES: /* This happens in layout_grid() */
176     break;
177   case BEHOLDER: /* This happens in layout_geodesic() */
178     break;
179
180   default:
181     abort();
182   }
183
184   f->focus = ((random() % 8) ? ROTATE :
185               (random() % 4) ? TRACK : SPIN);
186   f->track.x = 8 - frand(16);
187   f->track.y = 8 - frand(16);
188   f->track.z = 8 + frand(16);
189
190   f->tilt = 45 - BELLRAND(90);
191   f->roll = frand(180);
192   f->dilation.to = f->dilation.from = f->dilation.current = frand(1.0);
193   f->dilation.tick = 1;
194
195   f->scale = 0.8 + BELLRAND(0.2);
196
197   if      (bp->nfloaters == 1)  f->scale *= 0.5;
198   else if (bp->nfloaters <= 3)  f->scale *= 0.4;
199   else if (bp->nfloaters <= 9)  f->scale *= 0.3;
200   else if (bp->nfloaters <= 15) f->scale *= 0.2;
201   else if (bp->nfloaters <= 25) f->scale *= 0.15;
202   else if (bp->nfloaters <= 90) f->scale *= 0.12;
203   else                          f->scale *= 0.07;
204
205   if (MI_WIDTH(mi) < MI_HEIGHT(mi))
206     {
207       f->scale /= MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi) * 1.2;
208     }
209
210   {
211     static const struct { GLfloat pct; unsigned long c; } c[] = {
212       /* All of the articles that I found with percentages in them only
213          added up to around 70%, so who knows what that means. */
214 # if 0
215       { 55,   0x985A07 },  /* brown -- supposedly real global percentage */
216 # else
217       { 20,   0x985A07 },  /* brown -- but that's a lot of brown... */
218 # endif
219       {  8,   0xD5AD68 },  /* hazel */
220       {  8,   0x777F92 },  /* blue  */
221       {  2,   0x6B7249 },  /* green */
222       {  1,   0x7F7775 },  /* gray  */
223       {  0.5, 0x9E8042 },  /* amber */
224       {  0.1, 0xFFAA88 },  /* red   */
225     };
226     GLfloat p = 0, t = 0;
227     GLfloat s = 1 - frand(0.3);
228     int i;
229     for (i = 0; i < countof(c); i++)
230       p += c[i].pct;
231     p = frand(p);
232
233     for (i = 0; i < countof(c) - 1; i++)
234       {
235         t += c[i].pct;
236         if (t > p) break;
237       }
238
239     if (c[i].c == 0xFFAA88)    f->jaundice = 2;
240     else if (!(random() % 20)) f->jaundice = 1;
241
242     f->color[0] = ((c[i].c >> 16) & 0xFF) / 255.0 * s;
243     f->color[1] = ((c[i].c >>  8) & 0xFF) / 255.0 * s;
244     f->color[2] = ((c[i].c >>  0) & 0xFF) / 255.0 * s;
245     f->color[3] = 1;
246   }
247 }
248
249
250 /* Place a grid of eyeballs on the screen, maximizing use of space.
251  */
252 static void
253 layout_grid (ModeInfo *mi)
254 {
255   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
256
257   /* Distribute the eyes into a rectangular grid that fills the window.
258      There may be some empty cells.  N items in a W x H rectangle:
259      N = W * H
260      N = W * W * R
261      N/R = W*W
262      W = sqrt(N/R)
263   */
264   GLfloat aspect = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
265   int nlines = sqrt (bp->nfloaters / aspect) + 0.5;
266   int *cols = (int *) calloc (nlines, sizeof(*cols));
267   int i, x, y, max = 0;
268   GLfloat scale, spacing;
269
270   for (i = 0; i < bp->nfloaters; i++)
271     {
272       cols[i % nlines]++;
273       if (cols[i % nlines] > max) max = cols[i % nlines];
274     }
275
276   /* That gave us, e.g. 7777666. Redistribute to 6767767. */ 
277   for (i = 0; i < nlines / 2; i += 2)
278     {
279       int j = nlines-i-1;
280       int swap = cols[i];
281       cols[i] = cols[j];
282       cols[j] = swap;
283     }
284
285   scale = 1.0 / nlines;       /* Scale for height */
286   if (scale * max > aspect)   /* Shrink if overshot width */
287     scale *= aspect / (scale * max);
288
289   scale *= 0.9;           /* Add padding */
290   spacing = scale * 2.2;
291
292   if (bp->nfloaters == 1) spacing = 0;
293
294   i = 0;
295   for (y = 0; y < nlines; y++)
296     for (x = 0; x < cols[y]; x++)
297       {
298         floater *f = &bp->floaters[i];
299         f->scale = scale;
300         f->x = spacing * (x - cols[y] / 2.0) + spacing/2;
301         f->y = spacing * (y - nlines  / 2.0) + spacing/2;
302         f->z = 0;
303         i++;
304       }
305   free (cols);
306 }
307
308
309 /* Computes the midpoint of a line between two polar coords.
310  */
311 static void
312 midpoint2 (LL v1, LL v2, LL *vm_ret,
313            XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
314 {
315   XYZ p1, p2, pm;
316   LL vm;
317   GLfloat hyp;
318
319   p1.x = cos (v1.a) * cos (v1.o);
320   p1.y = cos (v1.a) * sin (v1.o);
321   p1.z = sin (v1.a);
322
323   p2.x = cos (v2.a) * cos (v2.o);
324   p2.y = cos (v2.a) * sin (v2.o);
325   p2.z = sin (v2.a);
326
327   pm.x = (p1.x + p2.x) / 2;
328   pm.y = (p1.y + p2.y) / 2;
329   pm.z = (p1.z + p2.z) / 2;
330
331   vm.o = atan2 (pm.y, pm.x);
332   hyp = sqrt (pm.x * pm.x + pm.y * pm.y);
333   vm.a = atan2 (pm.z, hyp);
334
335   *p1_ret = p1;
336   *p2_ret = p2;
337   *pm_ret = pm;
338   *vm_ret = vm;
339 }
340
341
342 /* Computes the midpoint of a triangle specified in polar coords.
343  */
344 static void
345 midpoint3 (LL v1, LL v2, LL v3, LL *vm_ret,
346            XYZ *p1_ret, XYZ *p2_ret, XYZ *p3_ret, XYZ *pm_ret)
347 {
348   XYZ p1, p2, p3, pm;
349   LL vm;
350   GLfloat hyp;
351
352   p1.x = cos (v1.a) * cos (v1.o);
353   p1.y = cos (v1.a) * sin (v1.o);
354   p1.z = sin (v1.a);
355
356   p2.x = cos (v2.a) * cos (v2.o);
357   p2.y = cos (v2.a) * sin (v2.o);
358   p2.z = sin (v2.a);
359
360   p3.x = cos (v3.a) * cos (v3.o);
361   p3.y = cos (v3.a) * sin (v3.o);
362   p3.z = sin (v3.a);
363
364   pm.x = (p1.x + p2.x + p3.x) / 3;
365   pm.y = (p1.y + p2.y + p3.y) / 3;
366   pm.z = (p1.z + p2.z + p3.z) / 3;
367
368   vm.o = atan2 (pm.y, pm.x);
369   hyp = sqrt (pm.x * pm.x + pm.y * pm.y);
370   vm.a = atan2 (pm.z, hyp);
371
372   *p1_ret = p1;
373   *p2_ret = p2;
374   *p3_ret = p3;
375   *pm_ret = pm;
376   *vm_ret = vm;
377 }
378
379
380 /* Place the eyeballs on a sphere (geodesic)
381  */
382 static void
383 layout_geodesic_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth,
384                           int *i)
385 {
386   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
387
388   if (depth <= 0)
389     {
390       floater *f = &bp->floaters[*i];
391       GLfloat s2 = 0.7;
392       LL vc;
393       XYZ p1, p2, p3, pc;
394       if (*i >= bp->nfloaters) abort();
395
396       midpoint3 (v1, v2, v3, &vc, &p1, &p2, &p3, &pc);
397
398       switch (bp->nfloaters) {     /* This is lame. */
399       case 20:   f->scale = 0.26;   break;
400       case 80:   f->scale = 0.13;   break;
401       case 320:  f->scale = 0.065;  break;
402       case 1280: f->scale = 0.0325; break;
403       default: abort();
404       }
405
406       f->z = s2 * cos (vc.a) * cos (vc.o);
407       f->x = s2 * cos (vc.a) * sin (vc.o);
408       f->y = s2 * sin (vc.a);
409       (*i)++;
410     }
411   else
412     {
413       LL v12, v23, v13;
414       XYZ p1, p2, p3, p12, p23, p13;
415
416       midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
417       midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
418       midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
419       depth--;
420
421       layout_geodesic_triangle (mi, v1,  v12, v13, depth, i);
422       layout_geodesic_triangle (mi, v12, v2,  v23, depth, i);
423       layout_geodesic_triangle (mi, v13, v23, v3,  depth, i);
424       layout_geodesic_triangle (mi, v12, v23, v13, depth, i);
425     }
426 }
427
428
429 /* Creates triangles of a geodesic to the given depth (frequency).
430  */
431 static void
432 layout_geodesic (ModeInfo *mi)
433 {
434   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
435   int depth;
436   GLfloat th0 = atan (0.5);  /* lat division: 26.57 deg */
437   GLfloat s = M_PI / 5;      /* lon division: 72 deg    */
438   int i;
439   int ii = 0;
440
441   switch (bp->nfloaters) {     /* This is lame. */
442   case 20:   depth = 0; break;
443   case 80:   depth = 1; break;
444   case 320:  depth = 2; break;
445   case 1280: depth = 3; break;
446   default: abort();
447   }
448
449   for (i = 0; i < 10; i++)
450     {
451       GLfloat th1 = s * i;
452       GLfloat th2 = s * (i+1);
453       GLfloat th3 = s * (i+2);
454       LL v1, v2, v3, vc;
455       v1.a = th0;    v1.o = th1;
456       v2.a = th0;    v2.o = th3;
457       v3.a = -th0;   v3.o = th2;
458       vc.a = M_PI/2; vc.o = th2;
459
460       if (i & 1)                        /* north */
461         {
462           layout_geodesic_triangle (mi, v1, v2, vc, depth, &ii);
463           layout_geodesic_triangle (mi, v2, v1, v3, depth, &ii);
464         }
465       else                              /* south */
466         {
467           v1.a = -v1.a;
468           v2.a = -v2.a;
469           v3.a = -v3.a;
470           vc.a = -vc.a;
471           layout_geodesic_triangle (mi, v2, v1, vc, depth, &ii);
472           layout_geodesic_triangle (mi, v1, v2, v3, depth, &ii);
473         }
474     }
475
476   bp->floaters[0].dx = BELLRAND(0.01) * RANDSIGN();
477 }
478
479
480 /* Advance the animation by one step.
481  */
482 static void
483 tick_floater (ModeInfo *mi, floater *f)
484 {
485   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
486
487   /* if (bp->button_down_p) return;*/
488
489   f->dx += f->ddx * speed * 0.5;
490   f->dy += f->ddy * speed * 0.5;
491   f->dz += f->ddz * speed * 0.5;
492
493   if (bp->mode != BEHOLDER)
494     {
495       f->x += f->dx * speed * 0.5;
496       f->y += f->dy * speed * 0.5;
497       f->z += f->dz * speed * 0.5;
498     }
499
500   f->dilation.tick += 0.1 * speed;
501   if (f->dilation.tick > 1) f->dilation.tick = 1;
502   if (f->dilation.tick < 0) f->dilation.tick = 0;
503
504   f->dilation.current = (f->dilation.from +
505                          ((f->dilation.to - f->dilation.from) *
506                           f->dilation.tick));
507
508   if (f->dilation.tick == 1 && !(random() % 20))
509     {
510       f->dilation.from = f->dilation.to;
511       f->dilation.to = frand(1.0);
512       f->dilation.tick = 0;
513     }
514
515   switch (bp->mode) {
516   case BOUNCE:
517     if (f->y < BOTTOM ||
518         f->x < LEFT || f->x > -LEFT)
519       reset_floater (mi, f);
520     break;
521   case SCROLL_LEFT:
522     if (f->x < LEFT)
523       reset_floater (mi, f);
524     break;
525   case SCROLL_RIGHT:
526     if (f->x > -LEFT)
527       reset_floater (mi, f);
528     break;
529   case XEYES:
530     break;
531   case BEHOLDER:
532     {
533       GLfloat x = f->x;
534       GLfloat y = f->z;
535       GLfloat th = atan2 (y, x);
536       GLfloat r = sqrt(x*x + y*y);
537       th += bp->floaters[0].dx;
538       f->x = r*cos(th);
539       f->z = r*sin(th);
540
541       if (! (random() % 100))
542         bp->floaters[0].dx += frand(0.0001) * RANDSIGN();
543     }
544     break;
545   default:
546     abort();
547   }
548 }
549
550
551 /* Make sure none of the eyeballs overlap.
552  */
553 static void
554 de_collide (ModeInfo *mi)
555 {
556   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
557   int i, j;
558   for (i = 0; i < bp->nfloaters; i++)
559     {
560       floater *f0 = &bp->floaters[i];
561       for (j = i+1; j < bp->nfloaters; j++)
562       {
563         floater *f1 = &bp->floaters[j];
564         GLfloat X = f1->x - f0->x;
565         GLfloat Y = f1->y - f0->y;
566         GLfloat Z = f1->z - f0->z;
567         GLfloat min = (f0->scale + f1->scale);
568         GLfloat d2 = X*X + Y*Y + Z*Z;
569         if (d2 < min*min)
570           {
571             GLfloat d   = sqrt (d2);
572             GLfloat dd = 0.5 * (min - d) / 2;
573             GLfloat dx = X * dd;
574             GLfloat dy = Y * dd;
575             GLfloat dz = Z * dd;
576             f0->x -= dx; f0->y -= dy; f0->z -= dz;
577             f1->x += dx; f1->y += dy; f1->z += dz;
578           }
579       }
580     }
581 }
582
583
584 /* Window management, etc
585  */
586 ENTRYPOINT void
587 reshape_peepers (ModeInfo *mi, int width, int height)
588 {
589   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
590   GLfloat h = (GLfloat) height / (GLfloat) width;
591   int y = 0;
592
593   glViewport (0, y, (GLint) width, (GLint) height);
594
595   glMatrixMode(GL_PROJECTION);
596   glLoadIdentity();
597   gluPerspective (30.0, 1/h, 1.0, 100);
598
599   glMatrixMode(GL_MODELVIEW);
600   glLoadIdentity();
601   gluLookAt( 0.0, 0.0, 30.0,
602              0.0, 0.0, 0.0,
603              0.0, 1.0, 0.0);
604
605   glClear(GL_COLOR_BUFFER_BIT);
606
607   if (bp->mode == XEYES)
608     layout_grid (mi);
609 }
610
611
612 /* Find the mouse pointer on the screen and note its position in the scene.
613  */
614 static void
615 track_mouse (ModeInfo *mi)
616 {
617   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
618   Window r, c;
619   int x, y, rx, ry;
620   unsigned int m;
621   int w = MI_WIDTH(mi);
622   int h = MI_HEIGHT(mi);
623   int rot = (int) current_device_rotation();
624   int swap;
625   GLfloat ys = 2.0;
626   GLfloat xs = ys * w / h;
627   time_t now = time ((time_t *) 0);
628
629   XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
630                  &r, &c, &rx, &ry, &x, &y, &m);
631
632   if (x != bp->last_mouse.x && y != bp->last_mouse.y)
633     {
634       bp->last_mouse_time = now;
635       bp->fake_mouse.x = x;
636       bp->fake_mouse.y = y;
637       bp->mouse_dx = 0;
638       bp->mouse_dy = 0;
639       bp->last_mouse.x = x;
640       bp->last_mouse.y = y;
641     }
642   else if (now > bp->last_mouse_time + 10)
643     {
644       /* Mouse isn't moving. Bored now. */
645       if (! (random() % 20)) bp->mouse_dx += (random() % 2) * RANDSIGN();
646       if (! (random() % 20)) bp->mouse_dy += (random() % 2) * RANDSIGN();
647       bp->fake_mouse.x += bp->mouse_dx;
648       bp->fake_mouse.y += bp->mouse_dy;
649       x = bp->fake_mouse.x;
650       y = bp->fake_mouse.y;
651     }
652
653   while (rot <= -180) rot += 360;
654   while (rot >   180) rot -= 360;
655
656   if (rot > 135 || rot < -135)          /* 180 */
657     {
658       x = w - x;
659       y = h - y;
660     }
661   else if (rot > 45)                    /* 90 */
662     {
663       swap = x; x = y; y = swap;
664       swap = w; w = h; h = swap;
665       xs = ys;
666       ys = xs * w / h;
667       x = w - x;
668     }
669   else if (rot < -45)                   /* 270 */
670     {
671       swap = x; x = y; y = swap;
672       swap = w; w = h; h = swap;
673       xs = ys;
674       ys = xs * w / h;
675       y = h - y;
676     }
677
678   /* Put the mouse directly on the glass. */
679   x = x - w / 2;
680   y = h / 2 - y;
681   bp->mouse.x = xs * x / w;
682   bp->mouse.y = ys * y / h;
683   bp->mouse.z = 0;
684
685 # if 0
686   glPushMatrix();
687   glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
688   if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
689   glColor3f(1,1,1);
690   glBegin(GL_LINES);
691   glVertex3f(-1,0,0); glVertex3f(1,0,0);
692   glVertex3f(0,-1,0); glVertex3f(0,1,0);
693   glVertex3f(0,0,-1); glVertex3f(0,0,1);
694   glEnd();
695   glPopMatrix();
696   if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
697 # endif
698
699   /* Move it farther into the scene: on the glass is too far away.
700      But keep it farther away the farther outside the window the
701      mouse is, so the eyes don''t turn 90 degrees sideways.
702    */
703   bp->mouse.x *= 0.8;
704   bp->mouse.y *= 0.8;
705   bp->mouse.z += 0.7;
706
707   bp->mouse.z = MAX (0.7, 
708                      sqrt (bp->mouse.x * bp->mouse.x +
709                            bp->mouse.y * bp->mouse.y));
710
711   if (bp->mode == BEHOLDER)
712     bp->mouse.z += 0.25;
713
714
715 # if 0
716   glPushMatrix();
717   glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
718   if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
719   glColor3f(1,0,1);
720   glBegin(GL_LINES);
721   glVertex3f(-1,0,0); glVertex3f(1,0,0);
722   glVertex3f(0,-1,0); glVertex3f(0,1,0);
723   glVertex3f(0,0,-1); glVertex3f(0,0,1);
724   glEnd();
725   glPopMatrix();
726   if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
727 # endif
728 }
729
730
731 ENTRYPOINT Bool
732 peepers_handle_event (ModeInfo *mi, XEvent *event)
733 {
734   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
735
736   if (gltrackball_event_handler (event, bp->trackball,
737                                  MI_WIDTH (mi), MI_HEIGHT (mi),
738                                  &bp->button_down_p))
739     {
740       if (bp->button_down_p)  /* Aim each eyeball at the mouse. */
741         {
742           int i;
743           track_mouse (mi);
744           for (i = 0; i < bp->nfloaters; i++)
745             {
746               floater *f = &bp->floaters[i];
747               f->track = bp->mouse;
748               f->focus = TRACK;
749             }
750         }
751
752       return True;
753     }
754
755   return False;
756 }
757
758
759 /* Generate the polygons for the display lists.
760    This routine generates the various styles of sphere-oid we use.
761  */
762 static int
763 draw_ball (ModeInfo *mi, component which)
764 {
765   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
766   int wire = MI_IS_WIREFRAME(mi);
767   int polys = 0;
768
769   GLfloat iris_ratio = 0.42;  /* Size of the iris. */
770   /* The lens bulges out, but the iris bulges in, sorta. */
771   GLfloat lens_bulge = (which == IRIS ? -0.50 : 0.32);
772
773   GLfloat xstep = 32;   /* Facets on the sphere */
774   GLfloat ystep = 32;
775   XYZ *stacks, *normals;
776   GLfloat x, y, z;
777   int i, j;
778   int xstart, xstop;
779
780   if (bp->nfloaters > 16 || wire)
781     xstep = ystep = 16;
782
783   if (bp->nfloaters > 96 && which == LENS)
784     return 0;
785
786   switch (which) {
787   case LENS:   xstart = 0; xstop = xstep; break;
788   case SCLERA: xstart = 0; xstop = xstep * (1 - iris_ratio/2); break;
789   case IRIS:   xstart = xstep * (1 - iris_ratio/2 * 1.2); xstop = xstep; break;
790   case RETINA: xstart = xstep * (1 - iris_ratio/2 * 1.2); xstop = 0; break;
791   default: abort(); break;
792   }
793
794   stacks  = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
795   normals = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
796
797   if (which == RETINA)
798     {
799       GLfloat c1[4] = { 0,    0, 0, 1 };
800       GLfloat c2[4] = { 0.15, 0, 0, 1 };
801       GLfloat th = M_PI * (1.0 - iris_ratio/2);
802       GLfloat z1 = cos(th);
803       GLfloat z2 = 0.9;
804       GLfloat r1 = sin(th);
805       GLfloat r2 = r1 * 0.3;
806
807       if (!wire)
808         {
809           glColor4fv (c1);
810           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c1);
811           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c1);
812         }
813       
814       /* Draw a black cone to occlude the interior of the eye. */
815
816       glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
817       for (i = 0; i <= xstep; i++)
818         {
819           GLfloat th2 = i * M_PI * 2 / xstep;
820           GLfloat x = cos(th2);
821           GLfloat y = sin(th2);
822           glNormal3f (0, 0, 1);
823           glVertex3f (z1, r1 * x, r1 * y);
824           glNormal3f (0, 0, 1);
825           glVertex3f (z2, r2 * x, r2 * y);
826           polys++;
827         }
828       glEnd();
829
830       if (!wire)
831         {
832           glColor4fv (c2);
833           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c2);
834           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c2);
835         }
836
837       /* Draw a small red circle at the base of the cone. */
838
839       glBegin (wire ? GL_LINES : GL_TRIANGLE_FAN);
840       glVertex3f (z2, 0, 0);
841       glNormal3f (0, 0, 1);
842       for (i = xstep; i >= 0; i--)
843         {
844           GLfloat th2 = i * M_PI * 2 / xstep;
845           GLfloat x = cos(th2);
846           GLfloat y = sin(th2);
847           glVertex3f (z2, r2 * x, r2 * y);
848           polys++;
849         }
850       glEnd();
851       goto DONE;
852     }
853
854   for (i = xstart; i <= xstop; i++)
855     {
856       GLfloat th = i * M_PI / xstep;
857       GLfloat x = cos(th);
858       GLfloat y = sin(th);
859
860       /* Bulge the lens, or dimple the iris. */
861       if (th > M_PI * (1.0 - iris_ratio/2) &&
862           th < M_PI * (1.0 + iris_ratio/2))
863         {
864           GLfloat r = (1 - th / M_PI) / iris_ratio * 2;
865           r = cos (M_PI * r / 2);
866           r *= lens_bulge;
867           r = r * r * (lens_bulge < 0 ? -1 : 1);
868           x *= 1+r;
869           y *= 1+r;
870         }
871
872       stacks[i].x = x;
873       stacks[i].y = y;
874       stacks[i].z = 0;
875     }
876
877   /* Fill normals with the normal at the center of each face. */
878   for (i = xstart; i < xstop; i++)
879     {
880       GLfloat dx = stacks[i+1].x - stacks[i].x;
881       GLfloat dy = stacks[i+1].y - stacks[i].y;
882       y = dy/dx;
883       z = sqrt (1 + y*y);
884       normals[i].x = -y/z;
885       normals[i].y =  1/z;
886       normals[i].z = 0;
887
888       if (lens_bulge < 0 && i > xstep * (1 - iris_ratio/2) + 1)
889         {
890           normals[i].x *= -1;
891           normals[i].y *= -1;
892         }
893     }
894
895   if (!wire)
896     glBegin (GL_QUADS);
897
898   for (i = xstart; i < xstop; i++)
899     {
900       GLfloat x0 = stacks[i].x;
901       GLfloat x1 = stacks[i+1].x;
902       GLfloat r0 = stacks[i].y;
903       GLfloat r1 = stacks[i+1].y;
904
905       for (j = 0; j < ystep*2; j++)
906         {
907           GLfloat tha = j     * M_PI / ystep;
908           GLfloat thb = (j+1) * M_PI / ystep;
909           GLfloat xa = cos (tha);
910           GLfloat ya = sin (tha);
911           GLfloat xb = cos (thb);
912           GLfloat yb = sin (thb);
913           
914           /* Each vertex normal is average of adjacent face normals. */
915
916           XYZ p1, p2, p3, p4;
917           XYZ n1, n2, n3, n4;
918           p1.x = x0; p1.y = r0 * ya; p1.z = r0 * xa;
919           p2.x = x1; p2.y = r1 * ya; p2.z = r1 * xa;
920           p3.x = x1; p3.y = r1 * yb; p3.z = r1 * xb;
921           p4.x = x0; p4.y = r0 * yb; p4.z = r0 * xb;
922
923           if (i == 0)
924             {
925               n1.x = 1; n1.y = 0; n1.z = 0;
926               n4.x = 1; n4.y = 0; n4.z = 0;
927             }
928           else
929             {
930               x = (normals[i-1].x + normals[i].x) / 2;
931               y = (normals[i-1].y + normals[i].y) / 2;
932               n1.x = x; n1.z = y * xa; n1.y = y * ya;
933               n4.x = x; n4.z = y * xb; n4.y = y * yb;
934             }
935
936           if (i == xstep-1)
937             {
938               n2.x = -1; n2.y = 0; n2.z = 0;
939               n3.x = -1; n3.y = 0; n3.z = 0;
940             }
941           else
942             {
943               x = (normals[i+1].x + normals[i].x) / 2;
944               y = (normals[i+1].y + normals[i].y) / 2;
945               n2.x = x; n2.z = y * xa; n2.y = y * ya;
946               n3.x = x; n3.z = y * xb; n3.y = y * yb;
947             }
948
949 #if 0
950           /* Render normals as lines for debugging */
951           glBegin(GL_LINES);
952           glVertex3f(p1.x, p1.y, p1.z);
953           glVertex3f(p1.x + n1.x * 0.3, p1.y + n1.y * 0.3, p1.z + n1.z * 0.3);
954           glEnd();
955
956           glBegin(GL_LINES);
957           glVertex3f(p2.x, p2.y, p2.z);
958           glVertex3f(p2.x + n2.x * 0.3, p2.y + n2.y * 0.3, p2.z + n2.z * 0.3);
959           glEnd();
960
961           glBegin(GL_LINES);
962           glVertex3f(p3.x, p3.y, p3.z);
963           glVertex3f(p3.x + n3.x * 0.3, p3.y + n3.y * 0.3, p3.z + n3.z * 0.3);
964           glEnd();
965
966           glBegin(GL_LINES);
967           glVertex3f(p4.x, p4.y, p4.z);
968           glVertex3f(p4.x + n4.x * 0.3, p4.y + n4.y * 0.3, p4.z + n4.z * 0.3);
969           glEnd();
970 #endif
971
972           if (wire)
973             glBegin (GL_LINE_LOOP);
974
975           glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
976                         (i - xstart) / (GLfloat) (xstop - xstart));
977
978           glNormal3f (n4.x, n4.y, n4.z);
979           glVertex3f (p4.x, p4.y, p4.z);
980
981           glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
982                         ((i+1) - xstart) / (GLfloat) (xstop - xstart));
983
984           glNormal3f (n3.x, n3.y, n3.z);
985           glVertex3f (p3.x, p3.y, p3.z);
986
987           glTexCoord2f (j / (GLfloat) ystep / 2,
988                         ((i+1) - xstart) / (GLfloat) (xstop - xstart));
989
990           glNormal3f (n2.x, n2.y, n2.z);
991           glVertex3f (p2.x, p2.y, p2.z);
992
993           glTexCoord2f (j / (GLfloat) ystep / 2,
994                         (i - xstart) / (GLfloat) (xstop - xstart));
995
996           glNormal3f (n1.x, n1.y, n1.z);
997           glVertex3f (p1.x, p1.y, p1.z);
998
999           polys++;
1000
1001           if (wire)
1002             glEnd();
1003         }
1004     }
1005
1006   if (!wire)
1007     glEnd();
1008
1009  DONE:
1010   free (stacks);
1011   free (normals);
1012
1013   return polys;
1014 }
1015
1016
1017 ENTRYPOINT void
1018 init_peepers (ModeInfo *mi)
1019 {
1020   peepers_configuration *bp;
1021   int wire = MI_IS_WIREFRAME(mi);
1022   int i;
1023
1024   MI_INIT (mi, bps);
1025
1026   bp = &bps[MI_SCREEN(mi)];
1027
1028   bp->glx_context = init_GL(mi);
1029
1030   reshape_peepers (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1031
1032   glShadeModel(GL_SMOOTH);
1033
1034   glEnable(GL_DEPTH_TEST);
1035   glEnable(GL_NORMALIZE);
1036
1037   if (!wire)
1038     {
1039       XImage *xi;
1040       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
1041       GLfloat amb[4] = {0.1, 0.1, 0.1, 1.0};
1042
1043       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1044       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1045
1046       glEnable (GL_LIGHTING);
1047       glEnable (GL_LIGHT0);
1048       glEnable (GL_DEPTH_TEST);
1049       glEnable (GL_CULL_FACE);
1050       glEnable (GL_BLEND);
1051
1052       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1053
1054       glLightfv(GL_LIGHT0, GL_POSITION, pos);
1055       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1056
1057       glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
1058
1059       xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual, 
1060                                  sclera_png, sizeof(sclera_png));
1061       glGenTextures (1, &bp->sclera_texture);
1062       glBindTexture (GL_TEXTURE_2D, bp->sclera_texture);
1063
1064       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1065       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1066       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1067       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1068
1069       glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
1070                     xi->width, xi->height, 0,
1071                     GL_RGBA, GL_UNSIGNED_BYTE, xi->data);
1072       check_gl_error("texture");
1073       XDestroyImage (xi);
1074
1075       xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual, 
1076                                  iris_png, sizeof(iris_png));
1077
1078       glGenTextures (1, &bp->iris_texture);
1079       glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
1080
1081       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1082       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1083       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1084       glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1085
1086       glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
1087                     xi->width, xi->height, 0,
1088                     GL_RGBA, GL_UNSIGNED_BYTE, xi->data);
1089       check_gl_error("texture");
1090       XDestroyImage (xi);
1091
1092
1093     }
1094
1095   bp->lens_list = glGenLists (1);
1096   glNewList (bp->lens_list, GL_COMPILE);
1097   bp->eye_polys += draw_ball (mi, LENS);
1098   glEndList ();
1099
1100   bp->sclera_list = glGenLists (1);
1101   glNewList (bp->sclera_list, GL_COMPILE);
1102   bp->eye_polys += draw_ball (mi, SCLERA);
1103   glEndList ();
1104
1105   bp->iris_list = glGenLists (1);
1106   glNewList (bp->iris_list, GL_COMPILE);
1107   bp->eye_polys += draw_ball (mi, IRIS);
1108   glEndList ();
1109
1110   bp->retina_list = glGenLists (1);
1111   glNewList (bp->retina_list, GL_COMPILE);
1112   bp->eye_polys += draw_ball (mi, RETINA);
1113   glEndList ();
1114
1115   bp->trackball = gltrackball_init (False);
1116
1117   if (!mode_opt || !*mode_opt || !strcasecmp (mode_opt, "random"))
1118     bp->mode = ((random() & 1) ? BOUNCE :
1119                 ((random() & 1) ? SCROLL_LEFT : SCROLL_RIGHT));
1120   else if (!strcasecmp (mode_opt, "bounce"))
1121     bp->mode = BOUNCE;
1122   else if (!strcasecmp (mode_opt, "scroll"))
1123     bp->mode = (random() & 1) ? SCROLL_LEFT : SCROLL_RIGHT;
1124   else if (!strcasecmp (mode_opt, "xeyes"))
1125     bp->mode = XEYES;
1126   else if (!strcasecmp (mode_opt, "beholder") ||
1127            !strcasecmp (mode_opt, "ball"))
1128     bp->mode = BEHOLDER;
1129   else
1130     {
1131       fprintf (stderr,
1132                "%s: mode must be bounce, scroll, random, xeyes or beholder,"
1133                " not \"%s\"\n", 
1134                progname, mode_opt);
1135       exit (1);
1136     }
1137
1138   bp->nfloaters = MI_COUNT (mi);
1139
1140   if (bp->nfloaters <= 0)
1141     {
1142       if (bp->mode == XEYES)
1143         bp->nfloaters = 2 + (random() % 30);
1144       else if (bp->mode == BEHOLDER)
1145         bp->nfloaters = 20 * pow (4, (random() % 4));
1146       else
1147         bp->nfloaters = 2 + (random() % 6);
1148     }
1149
1150   if (bp->mode == BEHOLDER)
1151     {
1152       if      (bp->nfloaters <= 20)  bp->nfloaters = 20;  /* This is lame */
1153       else if (bp->nfloaters <= 80)  bp->nfloaters = 80;
1154       else if (bp->nfloaters <= 320) bp->nfloaters = 320;
1155       else bp->nfloaters = 1280;
1156     }
1157
1158   bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
1159
1160   for (i = 0; i < bp->nfloaters; i++)
1161     {
1162       floater *f = &bp->floaters[i];
1163       f->idx = i;
1164       f->rot = make_rotator (10.0, 0, 0,
1165                              4, 0.05 * speed,
1166                              True);
1167       if (bp->nfloaters == 2)
1168         {
1169           f->x = 10 * (i ? 1 : -1);
1170         }
1171       else if (i != 0)
1172         {
1173           double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
1174           double r = LEFT * 0.3;
1175           f->x = r * cos(th);
1176           f->z = r * sin(th);
1177         }
1178
1179       if (bp->mode == SCROLL_LEFT || bp->mode == SCROLL_RIGHT)
1180         {
1181           f->y = f->x;
1182           f->x = 0;
1183         }
1184
1185       reset_floater (mi, f);
1186     }
1187
1188   if (bp->mode == XEYES)
1189     layout_grid (mi);
1190   else if (bp->mode == BEHOLDER)
1191     layout_geodesic (mi);
1192
1193 # ifndef HAVE_JWXYZ /* Real X11 */
1194 #  if 0 /* I wonder if this works? */
1195   if (bp->mode == XEYES && MI_WIN_IS_INWINDOW (mi))
1196     {
1197       uint32_t ca = 0;
1198       glClearColor (0, 0, 0, 0);
1199       XChangeProperty (MI_DISPLAY(mi), MI_WINDOW(mi),
1200                        XInternAtom (MI_DISPLAY(mi),
1201                                     "_NET_WM_WINDOW_OPACITY", 0),
1202                        XA_CARDINAL, 32, PropModeReplace,
1203                        (uint8_t *) &ca, 1);
1204     }
1205 #  endif
1206 # endif
1207 }
1208
1209
1210 static void
1211 draw_floater (ModeInfo *mi, floater *f, component which)
1212 {
1213   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1214   int wire = MI_IS_WIREFRAME(mi);
1215   double x, y, z;
1216
1217   GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0 };
1218   GLfloat c2[4]  = { 1.0, 1.0, 1.0, 1.0 };
1219   GLfloat c2b[4] = { 1.0, 0.6, 0.6, 1.0 };
1220   GLfloat c2c[4] = { 1.0, 1.0, 0.65, 1.0 };
1221   GLfloat c3[4]  = { 0.6, 0.6, 0.6, 0.25 };
1222
1223   get_position (f->rot, &x, &y, &z, 
1224                 which == LENS && !bp->button_down_p);
1225
1226   if (bp->nfloaters == 2 && 
1227       f != &bp->floaters[0] &&
1228       (bp->mode == BOUNCE || bp->mode == XEYES))
1229     {
1230       /* When there are exactly two eyes, track them together. */
1231       floater *f0 = &bp->floaters[0];
1232       double x0, y0, z0;
1233       get_position (f0->rot, &x0, &y0, &z0, 0);
1234       x = x0;
1235       y = 1-y0;  /* This is rotation: what the eye is looking at */
1236       z = z0;
1237       if (bp->mode != XEYES)
1238         {
1239           f->x = f0->x + f0->scale * 3;
1240           f->y = f0->y;
1241           f->z = f0->z;
1242         }
1243       f->dilation = f0->dilation;
1244       f->focus = f0->focus;
1245       f->track = f0->track;
1246       f->tilt = f0->tilt;
1247       f->scale = f0->scale;
1248       f->jaundice = f0->jaundice;
1249       if (f->focus == ROTATE)
1250         f->focus = f0->focus = TRACK;
1251       memcpy (f->color, f0->color, sizeof(f0->color));
1252     }
1253
1254   glPushMatrix();
1255   glTranslatef (f->x, f->y, f->z);
1256
1257   /* gltrackball_rotate (bp->trackball); */
1258
1259   switch (f->focus) {
1260   case ROTATE:
1261     glRotatef (y * 180, 0, 1, 0);
1262     glRotatef (f->tilt, 0, 0, 1);
1263     break;
1264   case SPIN:
1265     glRotatef (y * 360 + 90, 0, 1, 0);
1266     glRotatef (x * 360, 1.0, 0.0, 0.0);
1267     glRotatef (z * 360, 0.0, 0.0, 1.0);
1268     break;
1269   case TRACK:
1270     {
1271       GLfloat X, Y, Z;
1272       X = f->track.x - f->x;
1273       Y = f->track.z - f->z;
1274       Z = f->track.y - f->y;
1275       if (X != 0 || Y != 0)
1276         {
1277           GLfloat facing = atan2 (X, Y) * (180 / M_PI);
1278           GLfloat pitch  = atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI);
1279           glRotatef (90,     0, 1, 0);
1280           glRotatef (facing, 0, 1, 0);
1281           glRotatef (-pitch, 0, 0, 1);
1282         }
1283     }
1284
1285     break;
1286   default:
1287     abort();
1288   }
1289
1290   glRotatef (f->roll, 1, 0, 0);
1291   glScalef (f->scale, f->scale, f->scale);
1292
1293   if (! wire)
1294     glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1295
1296   switch (which) {
1297     case RETINA:
1298       if (!wire)
1299         {
1300           glScalef (0.96, 0.96, 0.96);
1301           glCallList (bp->retina_list);
1302         }
1303       break;
1304
1305     case IRIS:
1306       glColor4fv (f->color);
1307       if (! wire)
1308         {
1309           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  spc);
1310           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, 10);
1311
1312           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, f->color);
1313           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, 20);
1314
1315           glEnable (GL_TEXTURE_2D);
1316           glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
1317           glMatrixMode (GL_TEXTURE);
1318           glLoadIdentity();
1319           glScalef (1, 1.25 + f->dilation.current * 0.3, 1);
1320           glMatrixMode (GL_MODELVIEW);
1321         }
1322       glScalef (0.96, 0.96, 0.96);
1323       glCallList (bp->iris_list);
1324
1325       if (! wire)
1326         {
1327           glMatrixMode (GL_TEXTURE);
1328           glLoadIdentity();
1329           glMatrixMode (GL_MODELVIEW);
1330         }
1331       break;
1332
1333     case SCLERA:
1334       if (! wire)
1335         {
1336           GLfloat *c = (f->jaundice == 2 ? c2b : f->jaundice == 1 ? c2c : c2);
1337           glColor4fv (c);
1338           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
1339           glBindTexture (GL_TEXTURE_2D, bp->sclera_texture);
1340
1341           glScalef (0.98, 0.98, 0.98);
1342           glCallList (bp->sclera_list);
1343         }
1344       break;
1345
1346     case LENS:
1347       glColor4fv (c3);
1348       if (! wire)
1349         {
1350           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c3);
1351           glDisable (GL_TEXTURE_2D);
1352         }
1353       glCallList (bp->lens_list);
1354       break;
1355
1356   default:
1357     abort();
1358     break;
1359   }
1360
1361   glPopMatrix();
1362 }
1363
1364
1365 ENTRYPOINT void
1366 draw_peepers (ModeInfo *mi)
1367 {
1368   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1369   Display *dpy = MI_DISPLAY(mi);
1370   Window window = MI_WINDOW(mi);
1371
1372   if (!bp->glx_context)
1373     return;
1374
1375   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
1376
1377   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1378
1379   glPushMatrix ();
1380
1381   glRotatef (current_device_rotation(), 0, 0, 1);
1382
1383
1384   /* Scale so that screen is 1 high and w/h wide. */
1385   glScalef (8, 8, 8);
1386
1387   mi->polygon_count = 0;
1388
1389   if (bp->mode == XEYES || bp->mode == BEHOLDER)
1390     {
1391       int i;
1392       track_mouse (mi);
1393       for (i = 0; i < bp->nfloaters; i++)
1394         {
1395           floater *f = &bp->floaters[i];
1396           f->track = bp->mouse;
1397           f->focus = TRACK;
1398         }
1399     }
1400
1401 # if 0
1402   {
1403     /* Draw just one */
1404     component j;
1405     floater F;
1406     reset_floater(mi, &F);
1407     F.x = F.y = F.z = 0;
1408     F.dx = F.dy = F.dz = 0;
1409     F.ddx = F.ddy = F.ddz = 0;
1410     F.scale = 1;
1411     F.focus = TRACK;
1412     F.dilation.current = 0;
1413     F.track.x = F.track.y = F.track.z = 0;
1414     F.rot = make_rotator (0, 0, 0, 1, 0, False);
1415     glRotatef(180,0,1,0);
1416     glRotatef(15,1,0,0);
1417     for (j = RETINA; j <= LENS; j++)
1418       draw_floater (mi, &F, j);
1419     mi->polygon_count += bp->eye_polys;
1420   }
1421 # else
1422   {
1423     component j;
1424     int i;
1425     for (j = RETINA; j <= TICK; j++)
1426       for (i = 0; i < bp->nfloaters; i++)
1427         {
1428           floater *f = &bp->floaters[i];
1429           if (j == TICK)
1430             tick_floater (mi, f);
1431           else
1432             draw_floater (mi, f, j);
1433         }
1434
1435     if (bp->mode != BEHOLDER)
1436       de_collide (mi);
1437
1438     mi->polygon_count += bp->eye_polys * bp->nfloaters;
1439   }
1440 # endif
1441
1442   glPopMatrix ();
1443
1444   if (mi->fps_p) do_fps (mi);
1445   glFinish();
1446
1447   glXSwapBuffers(dpy, window);
1448 }
1449
1450
1451 ENTRYPOINT void
1452 free_peepers (ModeInfo *mi)
1453 {
1454   peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1455   int i;
1456   if (!bp->glx_context) return;
1457   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
1458   for (i = 0; i < bp->nfloaters; i++)
1459     free_rotator (bp->floaters[i].rot);
1460   if (bp->floaters) free (bp->floaters);
1461   if (glIsList(bp->lens_list)) glDeleteLists(bp->lens_list, 1);
1462   if (glIsList(bp->sclera_list)) glDeleteLists(bp->sclera_list, 1);
1463   if (glIsList(bp->iris_list)) glDeleteLists(bp->iris_list, 1);
1464   if (glIsList(bp->retina_list)) glDeleteLists(bp->retina_list, 1);
1465   if (bp->sclera_texture) glDeleteTextures (1, &bp->sclera_texture);
1466   if (bp->iris_texture) glDeleteTextures (1, &bp->iris_texture);
1467 }
1468
1469 XSCREENSAVER_MODULE ("Peepers", peepers)
1470
1471 #endif /* USE_GL */