1 /* peepers, Copyright (c) 2018 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 free_peepers 0
26 # define release_peepers 0
28 #define DEF_SPEED "1.0"
29 #define DEF_MODE "random"
32 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
37 #define RANDSIGN() ((random() & 1) ? 1 : -1)
39 #include "xlockmore.h"
42 #include "gltrackball.h"
43 #include "ximage-loader.h"
47 # include <X11/Xatom.h>
50 #include "images/gen/sclera_png.h"
51 #include "images/gen/iris_png.h"
53 #ifdef USE_GL /* whole file */
55 typedef struct { double a, o; } LL; /* latitude + longitude */
61 GLfloat ddx, ddy, ddz;
63 struct { GLfloat from, to, current, tick; } dilation;
64 enum { ROTATE, SPIN, TRACK } focus;
72 typedef enum { RETINA, IRIS, SCLERA, LENS, TICK } component;
75 GLXContext *glx_context;
76 trackball_state *trackball;
79 XYZ mouse, last_mouse, fake_mouse;
80 time_t last_mouse_time;
81 int mouse_dx, mouse_dy;
83 GLuint retina_list, sclera_list, lens_list, iris_list;
84 GLuint sclera_texture, iris_texture;
89 enum { BOUNCE, SCROLL_LEFT, SCROLL_RIGHT, XEYES, BEHOLDER } mode;
91 } peepers_configuration;
93 static peepers_configuration *bps = NULL;
98 static XrmOptionDescRec opts[] = {
99 { "-speed", ".speed", XrmoptionSepArg, 0 },
100 { "-mode", ".mode", XrmoptionSepArg, 0 },
103 static argtype vars[] = {
104 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
105 {&mode_opt, "mode", "Mode", DEF_MODE, t_String},
108 ENTRYPOINT ModeSpecOpt peepers_opts = {countof(opts), opts, countof(vars), vars, NULL};
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))
116 reset_floater (ModeInfo *mi, floater *f)
118 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
119 GLfloat r = ((bp->mode == BOUNCE ? LEFT : BOTTOM) *
120 (bp->nfloaters < 10 ? 0.3: 0.6));
123 if (bp->nfloaters <= 2)
125 x = frand(LEFT) * RANDSIGN() * 0.3;
130 /* Position them off screen in a circle */
131 GLfloat th = f->idx * (M_PI + (M_PI/6)) * 2 / bp->nfloaters;
133 y = r * sin (th) * 1.5; /* Oval */
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. */
151 GLfloat min = -0.004;
152 GLfloat max = -0.0019;
153 f->ddy = min + frand (max - min);
158 if (! (random() % (10 * bp->nfloaters)))
160 f->dx = BELLRAND(0.03) * RANDSIGN();
161 f->dz = BELLRAND(0.03) * RANDSIGN();
168 f->x = (bp->mode == SCROLL_LEFT ? -LEFT : LEFT);
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();
179 case XEYES: /* This happens in layout_grid() */
181 case BEHOLDER: /* This happens in layout_geodesic() */
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);
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;
199 f->scale = 0.8 + BELLRAND(0.2);
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;
209 if (MI_WIDTH(mi) < MI_HEIGHT(mi))
211 f->scale /= MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi) * 1.2;
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. */
219 { 55, 0x985A07 }, /* brown -- supposedly real global percentage */
221 { 20, 0x985A07 }, /* brown -- but that's a lot of brown... */
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 */
230 GLfloat p = 0, t = 0;
231 GLfloat s = 1 - frand(0.3);
233 for (i = 0; i < countof(c); i++)
237 for (i = 0; i < countof(c); i++)
243 if (c[i].c == 0xFFAA88) f->jaundice = 2;
244 else if (!(random() % 20)) f->jaundice = 1;
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;
254 /* Place a grid of eyeballs on the screen, maximizing use of space.
257 layout_grid (ModeInfo *mi)
259 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
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:
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;
274 for (i = 0; i < bp->nfloaters; i++)
277 if (cols[i % nlines] > max) max = cols[i % nlines];
280 /* That gave us, e.g. 7777666. Redistribute to 6767767. */
281 for (i = 0; i < nlines / 2; i += 2)
289 scale = 1.0 / nlines; /* Scale for height */
290 if (scale * max > aspect) /* Shrink if overshot width */
291 scale *= aspect / (scale * max);
293 scale *= 0.9; /* Add padding */
294 spacing = scale * 2.2;
296 if (bp->nfloaters == 1) spacing = 0;
299 for (y = 0; y < nlines; y++)
300 for (x = 0; x < cols[y]; x++)
302 floater *f = &bp->floaters[i];
304 f->x = spacing * (x - cols[y] / 2.0) + spacing/2;
305 f->y = spacing * (y - nlines / 2.0) + spacing/2;
313 /* Computes the midpoint of a line between two polar coords.
316 midpoint2 (LL v1, LL v2, LL *vm_ret,
317 XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
323 p1.x = cos (v1.a) * cos (v1.o);
324 p1.y = cos (v1.a) * sin (v1.o);
327 p2.x = cos (v2.a) * cos (v2.o);
328 p2.y = cos (v2.a) * sin (v2.o);
331 pm.x = (p1.x + p2.x) / 2;
332 pm.y = (p1.y + p2.y) / 2;
333 pm.z = (p1.z + p2.z) / 2;
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);
346 /* Computes the midpoint of a triangle specified in polar coords.
349 midpoint3 (LL v1, LL v2, LL v3, LL *vm_ret,
350 XYZ *p1_ret, XYZ *p2_ret, XYZ *p3_ret, XYZ *pm_ret)
356 p1.x = cos (v1.a) * cos (v1.o);
357 p1.y = cos (v1.a) * sin (v1.o);
360 p2.x = cos (v2.a) * cos (v2.o);
361 p2.y = cos (v2.a) * sin (v2.o);
364 p3.x = cos (v3.a) * cos (v3.o);
365 p3.y = cos (v3.a) * sin (v3.o);
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;
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);
384 /* Place the eyeballs on a sphere (geodesic)
387 layout_geodesic_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth,
390 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
394 floater *f = &bp->floaters[*i];
398 if (*i >= bp->nfloaters) abort();
400 midpoint3 (v1, v2, v3, &vc, &p1, &p2, &p3, &pc);
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;
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);
418 XYZ p1, p2, p3, p12, p23, p13;
420 midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
421 midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
422 midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
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);
433 /* Creates triangles of a geodesic to the given depth (frequency).
436 layout_geodesic (ModeInfo *mi)
438 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
440 GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */
441 GLfloat s = M_PI / 5; /* lon division: 72 deg */
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;
453 for (i = 0; i < 10; i++)
456 GLfloat th2 = s * (i+1);
457 GLfloat th3 = s * (i+2);
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;
464 if (i & 1) /* north */
466 layout_geodesic_triangle (mi, v1, v2, vc, depth, &ii);
467 layout_geodesic_triangle (mi, v2, v1, v3, depth, &ii);
475 layout_geodesic_triangle (mi, v2, v1, vc, depth, &ii);
476 layout_geodesic_triangle (mi, v1, v2, v3, depth, &ii);
480 bp->floaters[0].dx = BELLRAND(0.01) * RANDSIGN();
484 /* Advance the animation by one step.
487 tick_floater (ModeInfo *mi, floater *f)
489 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
491 /* if (bp->button_down_p) return;*/
493 f->dx += f->ddx * speed * 0.5;
494 f->dy += f->ddy * speed * 0.5;
495 f->dz += f->ddz * speed * 0.5;
497 if (bp->mode != BEHOLDER)
499 f->x += f->dx * speed * 0.5;
500 f->y += f->dy * speed * 0.5;
501 f->z += f->dz * speed * 0.5;
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;
508 f->dilation.current = (f->dilation.from +
509 ((f->dilation.to - f->dilation.from) *
512 if (f->dilation.tick == 1 && !(random() % 20))
514 f->dilation.from = f->dilation.to;
515 f->dilation.to = frand(1.0);
516 f->dilation.tick = 0;
522 f->x < LEFT || f->x > -LEFT)
523 reset_floater (mi, f);
527 reset_floater (mi, f);
531 reset_floater (mi, f);
539 GLfloat th = atan2 (y, x);
540 GLfloat r = sqrt(x*x + y*y);
541 th += bp->floaters[0].dx;
545 if (! (random() % 100))
546 bp->floaters[0].dx += frand(0.0001) * RANDSIGN();
555 /* Make sure none of the eyeballs overlap.
558 de_collide (ModeInfo *mi)
560 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
562 for (i = 0; i < bp->nfloaters; i++)
564 floater *f0 = &bp->floaters[i];
565 for (j = i+1; j < bp->nfloaters; j++)
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;
575 GLfloat d = sqrt (d2);
576 GLfloat dd = 0.5 * (min - d) / 2;
580 f0->x -= dx; f0->y -= dy; f0->z -= dz;
581 f1->x += dx; f1->y += dy; f1->z += dz;
588 /* Window management, etc
591 reshape_peepers (ModeInfo *mi, int width, int height)
593 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
594 GLfloat h = (GLfloat) height / (GLfloat) width;
597 glViewport (0, y, (GLint) width, (GLint) height);
599 glMatrixMode(GL_PROJECTION);
601 gluPerspective (30.0, 1/h, 1.0, 100);
603 glMatrixMode(GL_MODELVIEW);
605 gluLookAt( 0.0, 0.0, 30.0,
609 glClear(GL_COLOR_BUFFER_BIT);
611 if (bp->mode == XEYES)
616 /* Find the mouse pointer on the screen and note its position in the scene.
619 track_mouse (ModeInfo *mi)
621 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
625 int w = MI_WIDTH(mi);
626 int h = MI_HEIGHT(mi);
627 int rot = (int) current_device_rotation();
630 GLfloat xs = ys * w / h;
631 time_t now = time ((time_t *) 0);
633 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
634 &r, &c, &rx, &ry, &x, &y, &m);
636 if (x != bp->last_mouse.x && y != bp->last_mouse.y)
638 bp->last_mouse_time = now;
639 bp->fake_mouse.x = x;
640 bp->fake_mouse.y = y;
643 bp->last_mouse.x = x;
644 bp->last_mouse.y = y;
646 else if (now > bp->last_mouse_time + 10)
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;
657 while (rot <= -180) rot += 360;
658 while (rot > 180) rot -= 360;
660 if (rot > 135 || rot < -135) /* 180 */
665 else if (rot > 45) /* 90 */
667 swap = x; x = y; y = swap;
668 swap = w; w = h; h = swap;
673 else if (rot < -45) /* 270 */
675 swap = x; x = y; y = swap;
676 swap = w; w = h; h = swap;
682 /* Put the mouse directly on the glass. */
685 bp->mouse.x = xs * x / w;
686 bp->mouse.y = ys * y / h;
691 glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
692 if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
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);
700 if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
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.
711 bp->mouse.z = MAX (0.7,
712 sqrt (bp->mouse.x * bp->mouse.x +
713 bp->mouse.y * bp->mouse.y));
715 if (bp->mode == BEHOLDER)
721 glTranslatef (bp->mouse.x, bp->mouse.y, bp->mouse.z);
722 if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
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);
730 if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
736 peepers_handle_event (ModeInfo *mi, XEvent *event)
738 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
740 if (gltrackball_event_handler (event, bp->trackball,
741 MI_WIDTH (mi), MI_HEIGHT (mi),
744 if (bp->button_down_p) /* Aim each eyeball at the mouse. */
748 for (i = 0; i < bp->nfloaters; i++)
750 floater *f = &bp->floaters[i];
751 f->track = bp->mouse;
763 /* Generate the polygons for the display lists.
764 This routine generates the various styles of sphere-oid we use.
767 draw_ball (ModeInfo *mi, component which)
769 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
770 int wire = MI_IS_WIREFRAME(mi);
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);
777 GLfloat xstep = 32; /* Facets on the sphere */
779 XYZ *stacks, *normals;
784 if (bp->nfloaters > 16 || wire)
787 if (bp->nfloaters > 96 && which == LENS)
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;
798 stacks = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
799 normals = (XYZ *) calloc (sizeof(*stacks), xstep + 1);
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);
808 GLfloat r1 = sin(th);
809 GLfloat r2 = r1 * 0.3;
814 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c1);
815 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c1);
818 /* Draw a black cone to occlude the interior of the eye. */
820 glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
821 for (i = 0; i <= xstep; i++)
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);
837 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c2);
838 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, c2);
841 /* Draw a small red circle at the base of the cone. */
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--)
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);
858 for (i = xstart; i <= xstop; i++)
860 GLfloat th = i * M_PI / xstep;
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))
868 GLfloat r = (1 - th / M_PI) / iris_ratio * 2;
869 r = cos (M_PI * r / 2);
871 r = r * r * (lens_bulge < 0 ? -1 : 1);
881 /* Fill normals with the normal at the center of each face. */
882 for (i = xstart; i < xstop; i++)
884 GLfloat dx = stacks[i+1].x - stacks[i].x;
885 GLfloat dy = stacks[i+1].y - stacks[i].y;
892 if (lens_bulge < 0 && i > xstep * (1 - iris_ratio/2) + 1)
902 for (i = xstart; i < xstop; i++)
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;
909 for (j = 0; j < ystep*2; j++)
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);
918 /* Each vertex normal is average of adjacent face normals. */
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;
929 n1.x = 1; n1.y = 0; n1.z = 0;
930 n4.x = 1; n4.y = 0; n4.z = 0;
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;
942 n2.x = -1; n2.y = 0; n2.z = 0;
943 n3.x = -1; n3.y = 0; n3.z = 0;
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;
954 /* Render normals as lines for debugging */
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);
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);
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);
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);
977 glBegin (GL_LINE_LOOP);
979 glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
980 (i - xstart) / (GLfloat) (xstop - xstart));
982 glNormal3f (n4.x, n4.y, n4.z);
983 glVertex3f (p4.x, p4.y, p4.z);
985 glTexCoord2f ((j+1) / (GLfloat) ystep / 2,
986 ((i+1) - xstart) / (GLfloat) (xstop - xstart));
988 glNormal3f (n3.x, n3.y, n3.z);
989 glVertex3f (p3.x, p3.y, p3.z);
991 glTexCoord2f (j / (GLfloat) ystep / 2,
992 ((i+1) - xstart) / (GLfloat) (xstop - xstart));
994 glNormal3f (n2.x, n2.y, n2.z);
995 glVertex3f (p2.x, p2.y, p2.z);
997 glTexCoord2f (j / (GLfloat) ystep / 2,
998 (i - xstart) / (GLfloat) (xstop - xstart));
1000 glNormal3f (n1.x, n1.y, n1.z);
1001 glVertex3f (p1.x, p1.y, p1.z);
1021 init_peepers (ModeInfo *mi)
1023 peepers_configuration *bp;
1024 int wire = MI_IS_WIREFRAME(mi);
1029 bp = &bps[MI_SCREEN(mi)];
1031 bp->glx_context = init_GL(mi);
1033 reshape_peepers (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1035 glShadeModel(GL_SMOOTH);
1037 glEnable(GL_DEPTH_TEST);
1038 glEnable(GL_NORMALIZE);
1043 GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
1044 GLfloat amb[4] = {0.1, 0.1, 0.1, 1.0};
1046 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1047 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1049 glEnable (GL_LIGHTING);
1050 glEnable (GL_LIGHT0);
1051 glEnable (GL_DEPTH_TEST);
1052 glEnable (GL_CULL_FACE);
1053 glEnable (GL_BLEND);
1055 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1057 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1058 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1060 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
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);
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);
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");
1078 xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual,
1079 iris_png, sizeof(iris_png));
1081 glGenTextures (1, &bp->iris_texture);
1082 glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
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);
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");
1098 bp->lens_list = glGenLists (1);
1099 glNewList (bp->lens_list, GL_COMPILE);
1100 bp->eye_polys += draw_ball (mi, LENS);
1103 bp->sclera_list = glGenLists (1);
1104 glNewList (bp->sclera_list, GL_COMPILE);
1105 bp->eye_polys += draw_ball (mi, SCLERA);
1108 bp->iris_list = glGenLists (1);
1109 glNewList (bp->iris_list, GL_COMPILE);
1110 bp->eye_polys += draw_ball (mi, IRIS);
1113 bp->retina_list = glGenLists (1);
1114 glNewList (bp->retina_list, GL_COMPILE);
1115 bp->eye_polys += draw_ball (mi, RETINA);
1118 bp->trackball = gltrackball_init (False);
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"))
1125 else if (!strcasecmp (mode_opt, "scroll"))
1126 bp->mode = (random() & 1) ? SCROLL_LEFT : SCROLL_RIGHT;
1127 else if (!strcasecmp (mode_opt, "xeyes"))
1129 else if (!strcasecmp (mode_opt, "beholder") ||
1130 !strcasecmp (mode_opt, "ball"))
1131 bp->mode = BEHOLDER;
1135 "%s: mode must be bounce, scroll, random, xeyes or beholder,"
1137 progname, mode_opt);
1141 bp->nfloaters = MI_COUNT (mi);
1143 if (bp->nfloaters <= 0)
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));
1150 bp->nfloaters = 2 + (random() % 6);
1153 if (bp->mode == BEHOLDER)
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;
1161 bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
1163 for (i = 0; i < bp->nfloaters; i++)
1165 floater *f = &bp->floaters[i];
1167 f->rot = make_rotator (10.0, 0, 0,
1170 if (bp->nfloaters == 2)
1172 f->x = 10 * (i ? 1 : -1);
1176 double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
1177 double r = LEFT * 0.3;
1182 if (bp->mode == SCROLL_LEFT || bp->mode == SCROLL_RIGHT)
1188 reset_floater (mi, f);
1191 if (bp->mode == XEYES)
1193 else if (bp->mode == BEHOLDER)
1194 layout_geodesic (mi);
1196 # ifndef HAVE_JWXYZ /* Real X11 */
1197 # if 0 /* I wonder if this works? */
1198 if (bp->mode == XEYES && MI_WIN_IS_INWINDOW (mi))
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);
1214 draw_floater (ModeInfo *mi, floater *f, component which)
1216 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1217 int wire = MI_IS_WIREFRAME(mi);
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 };
1226 get_position (f->rot, &x, &y, &z,
1227 which == LENS && !bp->button_down_p);
1229 if (bp->nfloaters == 2 &&
1230 f != &bp->floaters[0] &&
1231 (bp->mode == BOUNCE || bp->mode == XEYES))
1233 /* When there are exactly two eyes, track them together. */
1234 floater *f0 = &bp->floaters[0];
1236 get_position (f0->rot, &x0, &y0, &z0, 0);
1238 y = 1-y0; /* This is rotation: what the eye is looking at */
1240 if (bp->mode != XEYES)
1242 f->x = f0->x + f0->scale * 3;
1246 f->dilation = f0->dilation;
1247 f->focus = f0->focus;
1248 f->track = f0->track;
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));
1258 glTranslatef (f->x, f->y, f->z);
1260 /* gltrackball_rotate (bp->trackball); */
1264 glRotatef (y * 180, 0, 1, 0);
1265 glRotatef (f->tilt, 0, 0, 1);
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);
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)
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);
1293 glRotatef (f->roll, 1, 0, 0);
1294 glScalef (f->scale, f->scale, f->scale);
1297 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1303 glScalef (0.96, 0.96, 0.96);
1304 glCallList (bp->retina_list);
1309 glColor4fv (f->color);
1312 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spc);
1313 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 10);
1315 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, f->color);
1316 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 20);
1318 glEnable (GL_TEXTURE_2D);
1319 glBindTexture (GL_TEXTURE_2D, bp->iris_texture);
1320 glMatrixMode (GL_TEXTURE);
1322 glScalef (1, 1.25 + f->dilation.current * 0.3, 1);
1323 glMatrixMode (GL_MODELVIEW);
1325 glScalef (0.96, 0.96, 0.96);
1326 glCallList (bp->iris_list);
1330 glMatrixMode (GL_TEXTURE);
1332 glMatrixMode (GL_MODELVIEW);
1339 GLfloat *c = (f->jaundice == 2 ? c2b : f->jaundice == 1 ? c2c : c2);
1341 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
1342 glBindTexture (GL_TEXTURE_2D, bp->sclera_texture);
1344 glScalef (0.98, 0.98, 0.98);
1345 glCallList (bp->sclera_list);
1353 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c3);
1354 glDisable (GL_TEXTURE_2D);
1356 glCallList (bp->lens_list);
1369 draw_peepers (ModeInfo *mi)
1371 peepers_configuration *bp = &bps[MI_SCREEN(mi)];
1372 Display *dpy = MI_DISPLAY(mi);
1373 Window window = MI_WINDOW(mi);
1375 if (!bp->glx_context)
1378 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1380 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1384 glRotatef (current_device_rotation(), 0, 0, 1);
1387 /* Scale so that screen is 1 high and w/h wide. */
1390 mi->polygon_count = 0;
1392 if (bp->mode == XEYES || bp->mode == BEHOLDER)
1396 for (i = 0; i < bp->nfloaters; i++)
1398 floater *f = &bp->floaters[i];
1399 f->track = bp->mouse;
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;
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;
1428 for (j = RETINA; j <= TICK; j++)
1429 for (i = 0; i < bp->nfloaters; i++)
1431 floater *f = &bp->floaters[i];
1433 tick_floater (mi, f);
1435 draw_floater (mi, f, j);
1438 if (bp->mode != BEHOLDER)
1441 mi->polygon_count += bp->eye_polys * bp->nfloaters;
1447 if (mi->fps_p) do_fps (mi);
1450 glXSwapBuffers(dpy, window);
1453 XSCREENSAVER_MODULE ("Peepers", peepers)