1 /* bouncingcow, Copyright (c) 2003-2019 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Boing, boing, boing.  Cow, cow, cow.
12  */
13 
14 #define DEFAULTS	"*delay:	30000       \n" \
15 			"*count:        1           \n" \
16 			"*showFPS:      False       \n" \
17 			"*wireframe:    False       \n" \
18 
19 # define release_cow 0
20 #define DEF_SPEED       "1.0"
21 #define DEF_TEXTURE     "(none)"
22 #define DEF_MATHEMATICAL "False"
23 
24 #undef countof
25 #define countof(x) (sizeof((x))/sizeof((*x)))
26 
27 #undef BELLRAND
28 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
29 #undef RANDSIGN
30 #define RANDSIGN() ((random() & 1) ? 1 : -1)
31 
32 #include "xlockmore.h"
33 #include "rotator.h"
34 #include "gltrackball.h"
35 #include "ximage-loader.h"
36 #include <ctype.h>
37 
38 #ifdef USE_GL /* whole file */
39 
40 #include "gllist.h"
41 
42 extern struct gllist
43  *cow_face, *cow_hide, *cow_hoofs, *cow_horns, *cow_tail, *cow_udder;
44 
45 static struct gllist **all_objs[] = {
46  &cow_face, &cow_hide, &cow_hoofs, &cow_horns, &cow_tail, &cow_udder
47 };
48 
49 #define FACE	0
50 #define HIDE	1
51 #define HOOFS	2
52 #define HORNS	3
53 #define TAIL	4
54 #define UDDER	5
55 
56 typedef struct {
57   GLfloat x, y, z;
58   GLfloat ix, iy, iz;
59   GLfloat dx, dy, dz;
60   GLfloat ddx, ddy, ddz;
61   rotator *rot;
62   Bool spinner_p;
63 } floater;
64 
65 typedef struct {
66   GLXContext *glx_context;
67   trackball_state *trackball;
68   Bool button_down_p;
69 
70   GLuint *dlists;
71   GLuint texture;
72   enum { BOUNCE, INFLATE, DEFLATE } mode;
73   GLfloat ratio;
74 
75   int nfloaters;
76   floater *floaters;
77 
78 } cow_configuration;
79 
80 static cow_configuration *bps = NULL;
81 
82 static GLfloat speed;
83 static const char *do_texture;
84 static Bool mathematical;
85 
86 static XrmOptionDescRec opts[] = {
87   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
88   {"-texture",     ".texture",   XrmoptionSepArg, 0 },
89   {"+texture",     ".texture",   XrmoptionNoArg, "(none)" },
90   {"-mathematical", ".mathematical", XrmoptionNoArg, "True" },
91   {"+mathematical", ".mathematical", XrmoptionNoArg, "False" },
92 };
93 
94 static argtype vars[] = {
95   {&speed,      "speed",      "Speed",   DEF_SPEED,     t_Float},
96   {&do_texture, "texture",    "Texture", DEF_TEXTURE,   t_String},
97   {&mathematical,"mathematical","Mathematical",DEF_MATHEMATICAL,t_Bool},
98 };
99 
100 ENTRYPOINT ModeSpecOpt cow_opts = {countof(opts), opts, countof(vars), vars, NULL};
101 
102 
103 #define BOTTOM 28.0
104 
105 static void
reset_floater(ModeInfo * mi,floater * f)106 reset_floater (ModeInfo *mi, floater *f)
107 {
108   cow_configuration *bp = &bps[MI_SCREEN(mi)];
109 
110   f->y = -BOTTOM;
111   f->x = f->ix;
112   f->z = f->iz;
113 
114   /* Yes, I know I'm varying the force of gravity instead of varying the
115      launch velocity.  That's intentional: empirical studies indicate
116      that it's way, way funnier that way. */
117 
118   f->dy = 5.0;
119   f->dx = 0;
120   f->dz = 0;
121 
122   /* -0.18 max  -0.3 top -0.4 middle  -0.6 bottom */
123   f->ddy = speed * (-0.6 + BELLRAND(0.45));
124   f->ddx = 0;
125   f->ddz = 0;
126 
127   f->spinner_p = !(random() % (12 * bp->nfloaters));
128 
129   if (! (random() % (30 * bp->nfloaters)))
130     {
131       f->dx = BELLRAND(1.8) * RANDSIGN();
132       f->dz = BELLRAND(1.8) * RANDSIGN();
133     }
134 }
135 
136 
137 static void
tick_floater(ModeInfo * mi,floater * f)138 tick_floater (ModeInfo *mi, floater *f)
139 {
140   cow_configuration *bp = &bps[MI_SCREEN(mi)];
141 
142   if (bp->button_down_p) return;
143 
144   f->dx += f->ddx;
145   f->dy += f->ddy;
146   f->dz += f->ddz;
147 
148   f->x += f->dx * speed;
149   f->y += f->dy * speed;
150   f->z += f->dz * speed;
151 
152   if (f->y < -BOTTOM ||
153       f->x < -BOTTOM*8 || f->x > BOTTOM*8 ||
154       f->z < -BOTTOM*8 || f->z > BOTTOM*8)
155     reset_floater (mi, f);
156 }
157 
158 
159 /* Window management, etc
160  */
161 ENTRYPOINT void
reshape_cow(ModeInfo * mi,int width,int height)162 reshape_cow (ModeInfo *mi, int width, int height)
163 {
164   GLfloat h = (GLfloat) height / (GLfloat) width;
165   int y = 0;
166 
167   if (width > height * 5) {   /* tiny window: show middle */
168     height = width * 9/16;
169     y = -height/2;
170     h = height / (GLfloat) width;
171   }
172 
173   glViewport (0, y, (GLint) width, (GLint) height);
174 
175   glMatrixMode(GL_PROJECTION);
176   glLoadIdentity();
177   gluPerspective (30.0, 1/h, 1.0, 100);
178 
179   glMatrixMode(GL_MODELVIEW);
180   glLoadIdentity();
181   gluLookAt( 0.0, 0.0, 30.0,
182              0.0, 0.0, 0.0,
183              0.0, 1.0, 0.0);
184 
185   glClear(GL_COLOR_BUFFER_BIT);
186 }
187 
188 
189 ENTRYPOINT Bool
cow_handle_event(ModeInfo * mi,XEvent * event)190 cow_handle_event (ModeInfo *mi, XEvent *event)
191 {
192   cow_configuration *bp = &bps[MI_SCREEN(mi)];
193 
194   if (gltrackball_event_handler (event, bp->trackball,
195                                  MI_WIDTH (mi), MI_HEIGHT (mi),
196                                  &bp->button_down_p))
197     return True;
198 
199   return False;
200 }
201 
202 
203 /* Textures
204  */
205 
206 static void
load_texture(ModeInfo * mi,const char * filename)207 load_texture (ModeInfo *mi, const char *filename)
208 {
209   cow_configuration *bp = &bps[MI_SCREEN(mi)];
210   Display *dpy = mi->dpy;
211   Visual *visual = mi->xgwa.visual;
212   char buf[1024];
213   XImage *image;
214 
215   bp->texture = 0;
216   if (MI_IS_WIREFRAME(mi))
217     return;
218 
219   if (!filename ||
220       !*filename ||
221       !strcasecmp (filename, "(none)"))
222     {
223       glDisable (GL_TEXTURE_2D);
224       return;
225     }
226 
227   image = file_to_ximage (dpy, visual, filename);
228   if (!image) return;
229 
230   clear_gl_error();
231   glGenTextures (1, &bp->texture);
232   glBindTexture (GL_TEXTURE_2D, bp->texture);
233   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
234                 image->width, image->height, 0,
235                 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
236   sprintf (buf, "texture: %.100s (%dx%d)",
237            filename, image->width, image->height);
238   check_gl_error(buf);
239 
240   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
241   glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
242 }
243 
244 
245 static void
render_cow(ModeInfo * mi,GLfloat ratio)246 render_cow (ModeInfo *mi, GLfloat ratio)
247 {
248   cow_configuration *bp = &bps[MI_SCREEN(mi)];
249   int wire = MI_IS_WIREFRAME(mi);
250   int i;
251   if (! bp->dlists)
252     bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
253   for (i = 0; i < countof(all_objs); i++)
254     {
255       if (bp->dlists[i])
256         glDeleteLists (bp->dlists[i], 1);
257       bp->dlists[i] = glGenLists (1);
258     }
259 
260   for (i = 0; i < countof(all_objs); i++)
261     {
262       GLfloat black[4] = {0, 0, 0, 1};
263       const struct gllist *gll = *all_objs[i];
264 
265       glNewList (bp->dlists[i], GL_COMPILE);
266 
267       glDisable (GL_TEXTURE_2D);
268 
269       if (i == HIDE)
270         {
271           GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
272           if (bp->texture)
273             {
274               /* if we have a texture, make the base color be white. */
275               color[0] = color[1] = color[2] = 1.0;
276 
277               glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
278               glTexGeni (GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
279               glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
280               glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
281               glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
282               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
283               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
284               glEnable(GL_TEXTURE_GEN_S);
285               glEnable(GL_TEXTURE_GEN_T);
286               glEnable(GL_TEXTURE_2D);
287 
288               /* approximately line it up with ../images/earth.png */
289               glMatrixMode (GL_TEXTURE);
290               glLoadIdentity();
291               glTranslatef (0.45, 0.58, 0);
292               glScalef (0.08, 0.16, 1);
293               glRotatef (-5, 0, 0, 1);
294               glMatrixMode (GL_MODELVIEW);
295             }
296           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
297           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
298           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
299        }
300       else if (i == TAIL)
301         {
302           GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
303           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
304           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
305           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
306         }
307       else if (i == UDDER)
308         {
309           GLfloat color[4] = {1.00, 0.53, 0.53, 1.00};
310           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
311           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
312           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
313         }
314       else if (i == HOOFS || i == HORNS)
315         {
316           GLfloat color[4] = {0.20, 0.20, 0.20, 1.00};
317           GLfloat spec[4]  = {0.30, 0.30, 0.30, 1.00};
318           GLfloat shiny    = 8.0;
319           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
320           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
321           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
322         }
323       else if (i == FACE)
324         {
325           GLfloat color[4] = {0.10, 0.10, 0.10, 1.00};
326           GLfloat spec[4]  = {0.10, 0.10, 0.10, 1.00};
327           GLfloat shiny    = 8.0;
328           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
329           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
330           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
331         }
332       else
333         {
334           GLfloat color[4] = {1.00, 1.00, 1.00, 1.00};
335           GLfloat spec[4]  = {1.00, 1.00, 1.00, 1.00};
336           GLfloat shiny    = 128.0;
337           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
338           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
339           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
340         }
341 
342       if (ratio == 0)
343         renderList (gll, wire);
344       else
345         {
346           /* Transition between a physics cow (cow-shaped) and a
347              mathematical cow (spherical).
348            */
349           struct gllist *gll2 = (struct gllist *) malloc (sizeof(*gll2));
350           GLfloat *p = (GLfloat *) malloc (gll->points * 6 * sizeof(*p));
351           GLfloat scale2 = 0.5 + (0.5 * (1-ratio));
352           const GLfloat *pin  = (GLfloat *) gll->data;
353           GLfloat *pout = p;
354           int j;
355           GLfloat scale = 10.46;
356 
357           memcpy (gll2, gll, sizeof(*gll2));
358           gll2->next = 0;
359           gll2->data = p;
360 
361           for (j = 0; j < gll2->points; j++)
362             {
363               const GLfloat *ppi;
364               GLfloat *ppo, d;
365               int k;
366               switch (gll2->format) {
367               case GL_N3F_V3F:
368 
369                 /* Verts transition from cow-shaped to the surface of
370                    the enclosing sphere. */
371                 ppi = &pin[3];
372                 ppo = &pout[3];
373                 d = sqrt (ppi[0]*ppi[0] + ppi[1]*ppi[1] + ppi[2]*ppi[2]);
374                 for (k = 0; k < 3; k++)
375                   {
376                     GLfloat min = ppi[k];
377                     GLfloat max = ppi[k] / d * scale;
378                     ppo[k] = (min + ratio * (max - min)) * scale2;
379                   }
380 
381                 /* Normals are the ratio between original normals and
382                    the radial coordinates. */
383                 ppi = &pin[0];
384                 ppo = &pout[0];
385                 for (k = 0; k < 3; k++)
386                   {
387                     GLfloat min = ppi[k];
388                     GLfloat max = ppi[k] / d;
389                     ppo[k] = (min + ratio * (max - min));
390                   }
391 
392                 pin  += 6;
393                 pout += 6;
394                 break;
395               default: abort(); break; /* write me */
396               }
397             }
398 
399           renderList (gll2, wire);
400           free (gll2);
401           free (p);
402         }
403 
404       glEndList ();
405     }
406 }
407 
408 
409 ENTRYPOINT void
init_cow(ModeInfo * mi)410 init_cow (ModeInfo *mi)
411 {
412   cow_configuration *bp;
413   int wire = MI_IS_WIREFRAME(mi);
414   int i;
415 
416   MI_INIT (mi, bps);
417 
418   bp = &bps[MI_SCREEN(mi)];
419 
420   bp->glx_context = init_GL(mi);
421 
422   reshape_cow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
423 
424   glShadeModel(GL_SMOOTH);
425 
426   glEnable(GL_DEPTH_TEST);
427   glEnable(GL_NORMALIZE);
428   glEnable(GL_CULL_FACE);
429 
430   if (!wire)
431     {
432       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
433 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
434       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
435       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
436       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
437 
438       glEnable(GL_LIGHTING);
439       glEnable(GL_LIGHT0);
440       glEnable(GL_DEPTH_TEST);
441       glEnable(GL_CULL_FACE);
442 
443       glLightfv(GL_LIGHT0, GL_POSITION, pos);
444       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
445       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
446       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
447     }
448 
449   bp->trackball = gltrackball_init (False);
450 
451   load_texture (mi, do_texture);
452 
453   bp->ratio = 0;
454   render_cow (mi, bp->ratio);
455 
456   bp->mode = BOUNCE;
457   bp->nfloaters = MI_COUNT (mi);
458   bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
459 
460   for (i = 0; i < bp->nfloaters; i++)
461     {
462       floater *f = &bp->floaters[i];
463       f->rot = make_rotator (10.0, 0, 0,
464                              4, 0.05 * speed,
465                              True);
466       if (bp->nfloaters == 2)
467         {
468           f->x = (i ? 6 : -6);
469         }
470       else if (i != 0)
471         {
472           double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
473           double r = 10;
474           f->x = r * cos(th);
475           f->z = r * sin(th);
476         }
477 
478       f->ix = f->x;
479       f->iy = f->y;
480       f->iz = f->z;
481       reset_floater (mi, f);
482     }
483 }
484 
485 
486 static void
draw_floater(ModeInfo * mi,floater * f)487 draw_floater (ModeInfo *mi, floater *f)
488 {
489   cow_configuration *bp = &bps[MI_SCREEN(mi)];
490   GLfloat n;
491   double x, y, z;
492 
493   get_position (f->rot, &x, &y, &z, !bp->button_down_p);
494 
495   glPushMatrix();
496   glTranslatef (f->x, f->y, f->z);
497 
498   gltrackball_rotate (bp->trackball);
499 
500   glRotatef (y * 360, 0.0, 1.0, 0.0);
501   if (f->spinner_p)
502     {
503       glRotatef (x * 360, 1.0, 0.0, 0.0);
504       glRotatef (z * 360, 0.0, 0.0, 1.0);
505     }
506 
507   n = 1.5;
508   if      (bp->nfloaters > 99) n *= 0.05;
509   else if (bp->nfloaters > 25) n *= 0.18;
510   else if (bp->nfloaters > 9)  n *= 0.3;
511   else if (bp->nfloaters > 1)  n *= 0.7;
512   glScalef(n, n, n);
513 
514   glCallList (bp->dlists[FACE]);
515   mi->polygon_count += (*all_objs[FACE])->points / 3;
516 
517   glCallList (bp->dlists[HIDE]);
518   mi->polygon_count += (*all_objs[HIDE])->points / 3;
519 
520   glCallList (bp->dlists[HOOFS]);
521   mi->polygon_count += (*all_objs[HOOFS])->points / 3;
522 
523   glCallList (bp->dlists[HORNS]);
524   mi->polygon_count += (*all_objs[HORNS])->points / 3;
525 
526   glCallList (bp->dlists[TAIL]);
527   mi->polygon_count += (*all_objs[TAIL])->points / 3;
528 
529   glCallList (bp->dlists[UDDER]);
530   mi->polygon_count += (*all_objs[UDDER])->points / 3;
531 
532   glPopMatrix();
533 }
534 
535 
536 
537 ENTRYPOINT void
draw_cow(ModeInfo * mi)538 draw_cow (ModeInfo *mi)
539 {
540   cow_configuration *bp = &bps[MI_SCREEN(mi)];
541   Display *dpy = MI_DISPLAY(mi);
542   Window window = MI_WINDOW(mi);
543   int i;
544 
545   if (!bp->glx_context)
546     return;
547 
548   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
549 
550   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
551 
552   glPushMatrix ();
553 
554 # ifdef HAVE_MOBILE	/* Keep it the same relative size when rotated. */
555   {
556     GLfloat h = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
557     int o = (int) current_device_rotation();
558     if (o != 0 && o != 180 && o != -180)
559       glScalef (1/h, 1/h, 1/h);
560     glRotatef(o, 0, 0, 1);
561   }
562 # endif
563 
564   glScalef (0.5, 0.5, 0.5);
565 
566   mi->polygon_count = 0;
567 
568   if (mathematical)
569     {
570       switch (bp->mode) {
571       case BOUNCE:
572         if (bp->ratio == 0 && !(random() % 400))
573           bp->mode = INFLATE;
574         else if (bp->ratio > 0 && !(random() % 2000))
575           bp->mode = DEFLATE;
576         break;
577       case INFLATE:
578         bp->ratio += 0.01;
579         if (bp->ratio >= 1)
580           {
581             bp->ratio = 1;
582             bp->mode = BOUNCE;
583           }
584         break;
585       case DEFLATE:
586         bp->ratio -= 0.01;
587         if (bp->ratio <= 0)
588           {
589             bp->ratio = 0;
590             bp->mode = BOUNCE;
591           }
592         break;
593       default:
594         abort();
595       }
596 
597       if (bp->ratio > 0)
598         render_cow (mi, bp->ratio);
599     }
600 
601 # if 0
602   {
603     floater F;
604     F.x = F.y = F.z = 0;
605     F.dx = F.dy = F.dz = 0;
606     F.ddx = F.ddy = F.ddz = 0;
607     F.rot = make_rotator (0, 0, 0, 1, 0, False);
608     glScalef(2,2,2);
609     draw_floater (mi, &F);
610   }
611 # else
612   for (i = 0; i < bp->nfloaters; i++)
613     {
614       /* "Don't kid yourself, Jimmy.  If a cow ever got the chance,
615          he'd eat you and everyone you care about!"
616              -- Troy McClure in "Meat and You: Partners in Freedom"
617        */
618       floater *f = &bp->floaters[i];
619       draw_floater (mi, f);
620       tick_floater (mi, f);
621     }
622 # endif
623 
624   glPopMatrix ();
625 
626   if (mi->fps_p) do_fps (mi);
627   glFinish();
628 
629   glXSwapBuffers(dpy, window);
630 }
631 
632 
633 ENTRYPOINT void
free_cow(ModeInfo * mi)634 free_cow (ModeInfo *mi)
635 {
636   cow_configuration *bp = &bps[MI_SCREEN(mi)];
637   int i;
638   if (!bp->glx_context) return;
639   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
640   if (bp->floaters) {
641     for (i = 0; i < bp->nfloaters; i++)
642       free_rotator (bp->floaters[i].rot);
643     free (bp->floaters);
644   }
645   for (i = 0; i < countof(all_objs); i++)
646     if (glIsList(bp->dlists[i])) glDeleteLists(bp->dlists[i], 1);
647   if (bp->trackball) gltrackball_free (bp->trackball);
648   if (bp->dlists) free (bp->dlists);
649 }
650 
651 XSCREENSAVER_MODULE_2 ("BouncingCow", bouncingcow, cow)
652 
653 #endif /* USE_GL */
654