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