1 /* peepers, Copyright (c) 2018-2019 Jamie Zawinski <jwz@jwz.org>
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
11 * Created: 14 Feb 2018, jwz.
15 * Inspired by @PaintYourDragon's Adafruit Snake Eyes Raspberry Pi Bonnet
16 * https://learn.adafruit.com/animated-snake-eyes-bonnet-for-raspberry-pi/
20 #define DEFAULTS "*delay: 30000 \n" \
22 "*showFPS: False \n" \
23 "*wireframe: False \n" \
25 # define release_peepers 0
27 #define DEF_SPEED "1.0"
28 #define DEF_MODE "random"
31 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
33 #define RANDSIGN() ((random() & 1) ? 1 : -1)
35 #include "xlockmore.h"
38 #include "gltrackball.h"
39 #include "ximage-loader.h"
43 # include <X11/Xatom.h>
46 #include "images/gen/sclera_png.h"
47 #include "images/gen/iris_png.h"
49 #ifdef USE_GL /* whole file */
51 typedef struct { double a, o; } LL; /* latitude + longitude */
57 GLfloat ddx, ddy, ddz;
59 struct { GLfloat from, to, current, tick; } dilation;
60 enum { ROTATE, SPIN, TRACK } focus;
68 typedef enum { RETINA, IRIS, SCLERA, LENS, TICK } component;
71 GLXContext *glx_context;
72 trackball_state *trackball;
75 XYZ mouse, last_mouse, fake_mouse;
76 time_t last_mouse_time;
77 int mouse_dx, mouse_dy;
79 GLuint retina_list, sclera_list, lens_list, iris_list;
80 GLuint sclera_texture, iris_texture;
85 enum { BOUNCE, SCROLL_LEFT, SCROLL_RIGHT, XEYES, BEHOLDER } mode;
87 } peepers_configuration;
89 static peepers_configuration *bps = NULL;
94 static XrmOptionDescRec opts[] = {
95 { "-speed", ".speed", XrmoptionSepArg, 0 },
96 { "-mode", ".mode", XrmoptionSepArg, 0 },
99 static argtype vars[] = {
100 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
101 {&mode_opt, "mode", "Mode", DEF_MODE, t_String},
104 ENTRYPOINT ModeSpecOpt peepers_opts = {countof(opts), opts, countof(vars), vars, NULL};
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))
112 reset_floater (ModeInfo *mi, floater *f)
114 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
115 GLfloat r = ((bp->mode == BOUNCE ? LEFT : BOTTOM) *
116 (bp->nfloaters < 10 ? 0.3: 0.6));
119 if (bp->nfloaters <= 2)
121 x = frand(LEFT) * RANDSIGN() * 0.3;
126 /* Position them off screen in a circle */
127 GLfloat th = f->idx * (M_PI + (M_PI/6)) * 2 / bp->nfloaters;
129 y = r * sin (th) * 1.5; /* Oval */
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. */
147 GLfloat min = -0.004;
148 GLfloat max = -0.0019;
149 f->ddy = min + frand (max - min);
154 if (! (random() % (10 * bp->nfloaters)))
156 f->dx = BELLRAND(0.03) * RANDSIGN();
157 f->dz = BELLRAND(0.03) * RANDSIGN();
164 f->x = (bp->mode == SCROLL_LEFT ? -LEFT : LEFT);
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();
175 case XEYES: /* This happens in layout_grid() */
177 case BEHOLDER: /* This happens in layout_geodesic() */
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);
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;
195 f->scale = 0.8 + BELLRAND(0.2);
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;
205 if (MI_WIDTH(mi) < MI_HEIGHT(mi))
207 f->scale /= MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi) * 1.2;
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. */
215 { 55, 0x985A07 }, /* brown -- supposedly real global percentage */
217 { 20, 0x985A07 }, /* brown -- but that's a lot of brown... */
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 */
226 GLfloat p = 0, t = 0;
227 GLfloat s = 1 - frand(0.3);
229 for (i = 0; i < countof(c); i++)
233 for (i = 0; i < countof(c) - 1; i++)
239 if (c[i].c == 0xFFAA88) f->jaundice = 2;
240 else if (!(random() % 20)) f->jaundice = 1;
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;
250 /* Place a grid of eyeballs on the screen, maximizing use of space.
253 layout_grid (ModeInfo *mi)
255 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
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:
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;
270 for (i = 0; i < bp->nfloaters; i++)
273 if (cols[i % nlines] > max) max = cols[i % nlines];
276 /* That gave us, e.g. 7777666. Redistribute to 6767767. */
277 for (i = 0; i < nlines / 2; i += 2)
285 scale = 1.0 / nlines; /* Scale for height */
286 if (scale * max > aspect) /* Shrink if overshot width */
287 scale *= aspect / (scale * max);
289 scale *= 0.9; /* Add padding */
290 spacing = scale * 2.2;
292 if (bp->nfloaters == 1) spacing = 0;
295 for (y = 0; y < nlines; y++)
296 for (x = 0; x < cols[y]; x++)
298 floater *f = &bp->floaters[i];
300 f->x = spacing * (x - cols[y] / 2.0) + spacing/2;
301 f->y = spacing * (y - nlines / 2.0) + spacing/2;
309 /* Computes the midpoint of a line between two polar coords.
312 midpoint2 (LL v1, LL v2, LL *vm_ret,
313 XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
319 p1.x = cos (v1.a) * cos (v1.o);
320 p1.y = cos (v1.a) * sin (v1.o);
323 p2.x = cos (v2.a) * cos (v2.o);
324 p2.y = cos (v2.a) * sin (v2.o);
327 pm.x = (p1.x + p2.x) / 2;
328 pm.y = (p1.y + p2.y) / 2;
329 pm.z = (p1.z + p2.z) / 2;
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);
342 /* Computes the midpoint of a triangle specified in polar coords.
345 midpoint3 (LL v1, LL v2, LL v3, LL *vm_ret,
346 XYZ *p1_ret, XYZ *p2_ret, XYZ *p3_ret, XYZ *pm_ret)
352 p1.x = cos (v1.a) * cos (v1.o);
353 p1.y = cos (v1.a) * sin (v1.o);
356 p2.x = cos (v2.a) * cos (v2.o);
357 p2.y = cos (v2.a) * sin (v2.o);
360 p3.x = cos (v3.a) * cos (v3.o);
361 p3.y = cos (v3.a) * sin (v3.o);
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;
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);
380 /* Place the eyeballs on a sphere (geodesic)
383 layout_geodesic_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth,
386 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
390 floater *f = &bp->floaters[*i];
394 if (*i >= bp->nfloaters) abort();
396 midpoint3 (v1, v2, v3, &vc, &p1, &p2, &p3, &pc);
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;
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);
414 XYZ p1, p2, p3, p12, p23, p13;
416 midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
417 midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
418 midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
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);
429 /* Creates triangles of a geodesic to the given depth (frequency).
432 layout_geodesic (ModeInfo *mi)
434 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
436 GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */
437 GLfloat s = M_PI / 5; /* lon division: 72 deg */
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;
449 for (i = 0; i < 10; i++)
452 GLfloat th2 = s * (i+1);
453 GLfloat th3 = s * (i+2);
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;
460 if (i & 1) /* north */
462 layout_geodesic_triangle (mi, v1, v2, vc, depth, &ii);
463 layout_geodesic_triangle (mi, v2, v1, v3, depth, &ii);
471 layout_geodesic_triangle (mi, v2, v1, vc, depth, &ii);
472 layout_geodesic_triangle (mi, v1, v2, v3, depth, &ii);
476 bp->floaters[0].dx = BELLRAND(0.01) * RANDSIGN();
480 /* Advance the animation by one step.
483 tick_floater (ModeInfo *mi, floater *f)
485 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
487 /* if (bp->button_down_p) return;*/
489 f->dx += f->ddx * speed * 0.5;
490 f->dy += f->ddy * speed * 0.5;
491 f->dz += f->ddz * speed * 0.5;
493 if (bp->mode != BEHOLDER)
495 f->x += f->dx * speed * 0.5;
496 f->y += f->dy * speed * 0.5;
497 f->z += f->dz * speed * 0.5;
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;
504 f->dilation.current = (f->dilation.from +
505 ((f->dilation.to - f->dilation.from) *
508 if (f->dilation.tick == 1 && !(random() % 20))
510 f->dilation.from = f->dilation.to;
511 f->dilation.to = frand(1.0);
512 f->dilation.tick = 0;
518 f->x < LEFT || f->x > -LEFT)
519 reset_floater (mi, f);
523 reset_floater (mi, f);
527 reset_floater (mi, f);
535 GLfloat th = atan2 (y, x);
536 GLfloat r = sqrt(x*x + y*y);
537 th += bp->floaters[0].dx;
541 if (! (random() % 100))
542 bp->floaters[0].dx += frand(0.0001) * RANDSIGN();
551 /* Make sure none of the eyeballs overlap.
554 de_collide (ModeInfo *mi)
556 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
558 for (i = 0; i < bp->nfloaters; i++)
560 floater *f0 = &bp->floaters[i];
561 for (j = i+1; j < bp->nfloaters; j++)
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;
571 GLfloat d = sqrt (d2);
572 GLfloat dd = 0.5 * (min - d) / 2;
576 f0->x -= dx; f0->y -= dy; f0->z -= dz;
577 f1->x += dx; f1->y += dy; f1->z += dz;
584 /* Window management, etc
587 reshape_peepers (ModeInfo *mi, int width, int height)
589 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
590 GLfloat h = (GLfloat) height / (GLfloat) width;
593 glViewport (0, y, (GLint) width, (GLint) height);
595 glMatrixMode(GL_PROJECTION);
597 gluPerspective (30.0, 1/h, 1.0, 100);
599 glMatrixMode(GL_MODELVIEW);
601 gluLookAt( 0.0, 0.0, 30.0,
605 glClear(GL_COLOR_BUFFER_BIT);
607 if (bp->mode == XEYES)
612 /* Find the mouse pointer on the screen and note its position in the scene.
615 track_mouse (ModeInfo *mi)
617 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
621 int w = MI_WIDTH(mi);
622 int h = MI_HEIGHT(mi);
623 int rot = (int) current_device_rotation();
626 GLfloat xs = ys * w / h;
627 time_t now = time ((time_t *) 0);
629 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
630 &r, &c, &rx, &ry, &x, &y, &m);
632 if (x != bp->last_mouse.x && y != bp->last_mouse.y)
634 bp->last_mouse_time = now;
635 bp->fake_mouse.x = x;
636 bp->fake_mouse.y = y;
639 bp->last_mouse.x = x;
640 bp->last_mouse.y = y;
642 else if (now > bp->last_mouse_time + 10)
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;
653 while (rot <= -180) rot += 360;
654 while (rot > 180) rot -= 360;
656 if (rot > 135 || rot < -135) /* 180 */
661 else if (rot > 45) /* 90 */
663 swap = x; x = y; y = swap;
664 swap = w; w = h; h = swap;
669 else if (rot < -45) /* 270 */
671 swap = x; x = y; y = swap;
672 swap = w; w = h; h = swap;
678 /* Put the mouse directly on the glass. */
681 bp->mouse.x = xs * x / w;
682 bp->mouse.y = ys * y / h;
687 glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
688 if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
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);
696 if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
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.
707 bp->mouse.z = MAX (0.7,
708 sqrt (bp->mouse.x * bp->mouse.x +
709 bp->mouse.y * bp->mouse.y));
711 if (bp->mode == BEHOLDER)
717 glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
718 if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
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);
726 if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
732 peepers_handle_event (ModeInfo *mi, XEvent *event)
734 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
736 if (gltrackball_event_handler (event, bp->trackball,
737 MI_WIDTH (mi), MI_HEIGHT (mi),
740 if (bp->button_down_p) /* Aim each eyeball at the mouse. */
744 for (i = 0; i < bp->nfloaters; i++)
746 floater *f = &bp->floaters[i];
747 f->track = bp->mouse;
759 /* Generate the polygons for the display lists.
760 This routine generates the various styles of sphere-oid we use.
763 draw_ball (ModeInfo *mi, component which)
765 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
766 int wire = MI_IS_WIREFRAME(mi);
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);
773 GLfloat xstep = 32; /* Facets on the sphere */
775 XYZ *stacks, *normals;
780 if (bp->nfloaters > 16 || wire)
783 if (bp->nfloaters > 96 && which == LENS)
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;
794 stacks = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
795 normals = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
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);
804 GLfloat r1 = sin(th);
805 GLfloat r2 = r1 * 0.3;
810 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c1);
811 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c1);
814 /* Draw a black cone to occlude the interior of the eye. */
816 glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
817 for (i = 0; i <= xstep; i++)
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);
833 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c2);
834 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c2);
837 /* Draw a small red circle at the base of the cone. */
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--)
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);
854 for (i = xstart; i <= xstop; i++)
856 GLfloat th = i * M_PI / xstep;
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))
864 GLfloat r = (1 - th / M_PI) / iris_ratio * 2;
865 r = cos (M_PI * r / 2);
867 r = r * r * (lens_bulge < 0 ? -1 : 1);
877 /* Fill normals with the normal at the center of each face. */
878 for (i = xstart; i < xstop; i++)
880 GLfloat dx = stacks[i+1].x - stacks[i].x;
881 GLfloat dy = stacks[i+1].y - stacks[i].y;
888 if (lens_bulge < 0 && i > xstep * (1 - iris_ratio/2) + 1)
898 for (i = xstart; i < xstop; i++)
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;
905 for (j = 0; j < ystep*2; j++)
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);
914 /* Each vertex normal is average of adjacent face normals. */
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;
925 n1.x = 1; n1.y = 0; n1.z = 0;
926 n4.x = 1; n4.y = 0; n4.z = 0;
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;
938 n2.x = -1; n2.y = 0; n2.z = 0;
939 n3.x = -1; n3.y = 0; n3.z = 0;
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;
950 /* Render normals as lines for debugging */
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);
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);
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);
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);
973 glBegin (GL_LINE_LOOP);
975 glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
976 (i - xstart) / (GLfloat) (xstop - xstart));
978 glNormal3f (n4.x, n4.y, n4.z);
979 glVertex3f (p4.x, p4.y, p4.z);
981 glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
982 ((i+1) - xstart) / (GLfloat) (xstop - xstart));
984 glNormal3f (n3.x, n3.y, n3.z);
985 glVertex3f (p3.x, p3.y, p3.z);
987 glTexCoord2f (j / (GLfloat) ystep / 2,
988 ((i+1) - xstart) / (GLfloat) (xstop - xstart));
990 glNormal3f (n2.x, n2.y, n2.z);
991 glVertex3f (p2.x, p2.y, p2.z);
993 glTexCoord2f (j / (GLfloat) ystep / 2,
994 (i - xstart) / (GLfloat) (xstop - xstart));
996 glNormal3f (n1.x, n1.y, n1.z);
997 glVertex3f (p1.x, p1.y, p1.z);
1018 init_peepers (ModeInfo *mi)
1020 peepers_configuration *bp;
1021 int wire = MI_IS_WIREFRAME(mi);
1026 bp = &bps[MI_SCREEN(mi)];
1028 bp->glx_context = init_GL(mi);
1030 reshape_peepers (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1032 glShadeModel(GL_SMOOTH);
1034 glEnable(GL_DEPTH_TEST);
1035 glEnable(GL_NORMALIZE);
1040 GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
1041 GLfloat amb[4] = {0.1, 0.1, 0.1, 1.0};
1043 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1044 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1046 glEnable (GL_LIGHTING);
1047 glEnable (GL_LIGHT0);
1048 glEnable (GL_DEPTH_TEST);
1049 glEnable (GL_CULL_FACE);
1050 glEnable (GL_BLEND);
1052 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1054 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1055 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1057 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
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);
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);
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");
1075 xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual,
1076 iris_png, sizeof(iris_png));
1078 glGenTextures (1, &bp->iris_texture);
1079 glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
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);
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");
1095 bp->lens_list = glGenLists (1);
1096 glNewList (bp->lens_list, GL_COMPILE);
1097 bp->eye_polys += draw_ball (mi, LENS);
1100 bp->sclera_list = glGenLists (1);
1101 glNewList (bp->sclera_list, GL_COMPILE);
1102 bp->eye_polys += draw_ball (mi, SCLERA);
1105 bp->iris_list = glGenLists (1);
1106 glNewList (bp->iris_list, GL_COMPILE);
1107 bp->eye_polys += draw_ball (mi, IRIS);
1110 bp->retina_list = glGenLists (1);
1111 glNewList (bp->retina_list, GL_COMPILE);
1112 bp->eye_polys += draw_ball (mi, RETINA);
1115 bp->trackball = gltrackball_init (False);
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"))
1122 else if (!strcasecmp (mode_opt, "scroll"))
1123 bp->mode = (random() & 1) ? SCROLL_LEFT : SCROLL_RIGHT;
1124 else if (!strcasecmp (mode_opt, "xeyes"))
1126 else if (!strcasecmp (mode_opt, "beholder") ||
1127 !strcasecmp (mode_opt, "ball"))
1128 bp->mode = BEHOLDER;
1132 "%s: mode must be bounce, scroll, random, xeyes or beholder,"
1134 progname, mode_opt);
1138 bp->nfloaters = MI_COUNT (mi);
1140 if (bp->nfloaters <= 0)
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));
1147 bp->nfloaters = 2 + (random() % 6);
1150 if (bp->mode == BEHOLDER)
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;
1158 bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
1160 for (i = 0; i < bp->nfloaters; i++)
1162 floater *f = &bp->floaters[i];
1164 f->rot = make_rotator (10.0, 0, 0,
1167 if (bp->nfloaters == 2)
1169 f->x = 10 * (i ? 1 : -1);
1173 double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
1174 double r = LEFT * 0.3;
1179 if (bp->mode == SCROLL_LEFT || bp->mode == SCROLL_RIGHT)
1185 reset_floater (mi, f);
1188 if (bp->mode == XEYES)
1190 else if (bp->mode == BEHOLDER)
1191 layout_geodesic (mi);
1193 # ifndef HAVE_JWXYZ /* Real X11 */
1194 # if 0 /* I wonder if this works? */
1195 if (bp->mode == XEYES && MI_WIN_IS_INWINDOW (mi))
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);
1211 draw_floater (ModeInfo *mi, floater *f, component which)
1213 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1214 int wire = MI_IS_WIREFRAME(mi);
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 };
1223 get_position (f->rot, &x, &y, &z,
1224 which == LENS && !bp->button_down_p);
1226 if (bp->nfloaters == 2 &&
1227 f != &bp->floaters[0] &&
1228 (bp->mode == BOUNCE || bp->mode == XEYES))
1230 /* When there are exactly two eyes, track them together. */
1231 floater *f0 = &bp->floaters[0];
1233 get_position (f0->rot, &x0, &y0, &z0, 0);
1235 y = 1-y0; /* This is rotation: what the eye is looking at */
1237 if (bp->mode != XEYES)
1239 f->x = f0->x + f0->scale * 3;
1243 f->dilation = f0->dilation;
1244 f->focus = f0->focus;
1245 f->track = f0->track;
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));
1255 glTranslatef (f->x, f->y, f->z);
1257 /* gltrackball_rotate (bp->trackball); */
1261 glRotatef (y * 180, 0, 1, 0);
1262 glRotatef (f->tilt, 0, 0, 1);
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);
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)
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);
1290 glRotatef (f->roll, 1, 0, 0);
1291 glScalef (f->scale, f->scale, f->scale);
1294 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1300 glScalef (0.96, 0.96, 0.96);
1301 glCallList (bp->retina_list);
1306 glColor4fv (f->color);
1309 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spc);
1310 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 10);
1312 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, f->color);
1313 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 20);
1315 glEnable (GL_TEXTURE_2D);
1316 glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
1317 glMatrixMode (GL_TEXTURE);
1319 glScalef (1, 1.25 + f->dilation.current * 0.3, 1);
1320 glMatrixMode (GL_MODELVIEW);
1322 glScalef (0.96, 0.96, 0.96);
1323 glCallList (bp->iris_list);
1327 glMatrixMode (GL_TEXTURE);
1329 glMatrixMode (GL_MODELVIEW);
1336 GLfloat *c = (f->jaundice == 2 ? c2b : f->jaundice == 1 ? c2c : c2);
1338 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
1339 glBindTexture (GL_TEXTURE_2D, bp->sclera_texture);
1341 glScalef (0.98, 0.98, 0.98);
1342 glCallList (bp->sclera_list);
1350 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c3);
1351 glDisable (GL_TEXTURE_2D);
1353 glCallList (bp->lens_list);
1366 draw_peepers (ModeInfo *mi)
1368 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1369 Display *dpy = MI_DISPLAY(mi);
1370 Window window = MI_WINDOW(mi);
1372 if (!bp->glx_context)
1375 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
1377 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1381 glRotatef (current_device_rotation(), 0, 0, 1);
1384 /* Scale so that screen is 1 high and w/h wide. */
1387 mi->polygon_count = 0;
1389 if (bp->mode == XEYES || bp->mode == BEHOLDER)
1393 for (i = 0; i < bp->nfloaters; i++)
1395 floater *f = &bp->floaters[i];
1396 f->track = bp->mouse;
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;
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;
1425 for (j = RETINA; j <= TICK; j++)
1426 for (i = 0; i < bp->nfloaters; i++)
1428 floater *f = &bp->floaters[i];
1430 tick_floater (mi, f);
1432 draw_floater (mi, f, j);
1435 if (bp->mode != BEHOLDER)
1438 mi->polygon_count += bp->eye_polys * bp->nfloaters;
1444 if (mi->fps_p) do_fps (mi);
1447 glXSwapBuffers(dpy, window);
1452 free_peepers (ModeInfo *mi)
1454 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
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);
1469 XSCREENSAVER_MODULE ("Peepers", peepers)