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