e96bc647982d449c6a72b5b0493cf3db607c450d
[xscreensaver] / hacks / glx / bouncingcow.c
1 /* bouncingcow, Copyright (c) 2003-2006 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 DEBUG */
20
21
22 # define refresh_cow 0
23 # define release_cow 0
24 #define DEF_SPEED       "1.0"
25 #define DEF_TEXTURE     "(none)"
26
27 #undef countof
28 #define countof(x) (sizeof((x))/sizeof((*x)))
29
30 #undef BELLRAND
31 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
32 #undef RANDSIGN
33 #define RANDSIGN() ((random() & 1) ? 1 : -1)
34
35 #include "xlockmore.h"
36 #include "rotator.h"
37 #include "gltrackball.h"
38 #include "xpm-ximage.h"
39 #include <ctype.h>
40
41 #ifdef USE_GL /* whole file */
42
43 #include "gllist.h"
44
45 extern struct gllist
46  *cow_face, *cow_hide, *cow_hoofs, *cow_horns, *cow_tail, *cow_udder;
47
48 static struct gllist **all_objs[] = {
49  &cow_face, &cow_hide, &cow_hoofs, &cow_horns, &cow_tail, &cow_udder
50 };
51
52 #define FACE    0
53 #define HIDE    1
54 #define HOOFS   2
55 #define HORNS   3
56 #define TAIL    4
57 #define UDDER   5
58
59 typedef struct {
60   GLfloat x, y, z;
61   GLfloat ix, iy, iz;
62   GLfloat dx, dy, dz;
63   GLfloat ddx, ddy, ddz;
64   rotator *rot;
65   Bool spinner_p;
66 } floater;
67
68 typedef struct {
69   GLXContext *glx_context;
70   trackball_state *trackball;
71   Bool button_down_p;
72
73   GLuint *dlists;
74   GLuint texture;
75
76   int nfloaters;
77   floater *floaters;
78
79 } cow_configuration;
80
81 static cow_configuration *bps = NULL;
82
83 static GLfloat speed;
84 static const char *do_texture;
85
86 static XrmOptionDescRec opts[] = {
87   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
88   {"-texture",     ".texture",   XrmoptionSepArg, 0 },
89   {"+texture",     ".texture",   XrmoptionNoArg, "(none)" },
90 };
91
92 static argtype vars[] = {
93   {&speed,      "speed",      "Speed",   DEF_SPEED,     t_Float},
94   {&do_texture, "texture",    "Texture", DEF_TEXTURE,   t_String},
95 };
96
97 ENTRYPOINT ModeSpecOpt cow_opts = {countof(opts), opts, countof(vars), vars, NULL};
98
99
100 #define BOTTOM 28.0
101
102 static void
103 reset_floater (ModeInfo *mi, floater *f)
104 {
105   cow_configuration *bp = &bps[MI_SCREEN(mi)];
106
107   f->y = -BOTTOM;
108   f->x = f->ix;
109   f->z = f->iz;
110
111   /* Yes, I know I'm varying the force of gravity instead of varying the
112      launch velocity.  That's intentional: empirical studies indicate
113      that it's way, way funnier that way. */
114
115   f->dy = 5.0;
116   f->dx = 0;
117   f->dz = 0;
118
119   /* -0.18 max  -0.3 top -0.4 middle  -0.6 bottom */
120   f->ddy = speed * (-0.6 + BELLRAND(0.45));
121   f->ddx = 0;
122   f->ddz = 0;
123
124   f->spinner_p = !(random() % (12 * bp->nfloaters));
125
126   if (! (random() % (30 * bp->nfloaters)))
127     {
128       f->dx = BELLRAND(1.8) * RANDSIGN();
129       f->dz = BELLRAND(1.8) * RANDSIGN();
130     }
131 }
132
133
134 static void
135 tick_floater (ModeInfo *mi, floater *f)
136 {
137   cow_configuration *bp = &bps[MI_SCREEN(mi)];
138
139   if (bp->button_down_p) return;
140
141   f->dx += f->ddx;
142   f->dy += f->ddy;
143   f->dz += f->ddz;
144
145   f->x += f->dx * speed;
146   f->y += f->dy * speed;
147   f->z += f->dz * speed;
148
149   if (f->y < -BOTTOM ||
150       f->x < -BOTTOM*8 || f->x > BOTTOM*8 ||
151       f->z < -BOTTOM*8 || f->z > BOTTOM*8)
152     reset_floater (mi, f);
153 }
154
155
156 /* Window management, etc
157  */
158 ENTRYPOINT void
159 reshape_cow (ModeInfo *mi, int width, int height)
160 {
161   GLfloat h = (GLfloat) height / (GLfloat) width;
162
163   glViewport (0, 0, (GLint) width, (GLint) height);
164
165   glMatrixMode(GL_PROJECTION);
166   glLoadIdentity();
167   gluPerspective (30.0, 1/h, 1.0, 100);
168
169   glMatrixMode(GL_MODELVIEW);
170   glLoadIdentity();
171   gluLookAt( 0.0, 0.0, 30.0,
172              0.0, 0.0, 0.0,
173              0.0, 1.0, 0.0);
174
175   glClear(GL_COLOR_BUFFER_BIT);
176 }
177
178
179 ENTRYPOINT Bool
180 cow_handle_event (ModeInfo *mi, XEvent *event)
181 {
182   cow_configuration *bp = &bps[MI_SCREEN(mi)];
183
184   if (event->xany.type == ButtonPress &&
185       event->xbutton.button == Button1)
186     {
187       bp->button_down_p = True;
188       gltrackball_start (bp->trackball,
189                          event->xbutton.x, event->xbutton.y,
190                          MI_WIDTH (mi), MI_HEIGHT (mi));
191       return True;
192     }
193   else if (event->xany.type == ButtonRelease &&
194            event->xbutton.button == Button1)
195     {
196       bp->button_down_p = False;
197       return True;
198     }
199   else if (event->xany.type == ButtonPress &&
200            (event->xbutton.button == Button4 ||
201             event->xbutton.button == Button5))
202     {
203       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
204                               !event->xbutton.state);
205       return True;
206     }
207   else if (event->xany.type == MotionNotify &&
208            bp->button_down_p)
209     {
210       gltrackball_track (bp->trackball,
211                          event->xmotion.x, event->xmotion.y,
212                          MI_WIDTH (mi), MI_HEIGHT (mi));
213       return True;
214     }
215
216   return False;
217 }
218
219
220 /* Textures
221  */
222
223 static Bool
224 load_texture (ModeInfo *mi, const char *filename)
225 {
226   Display *dpy = mi->dpy;
227   Visual *visual = mi->xgwa.visual;
228   Colormap cmap = mi->xgwa.colormap;
229   char buf[1024];
230   XImage *image;
231
232   if (MI_IS_WIREFRAME(mi))
233     return False;
234
235   if (!filename ||
236       !*filename ||
237       !strcasecmp (filename, "(none)"))
238     {
239       glDisable (GL_TEXTURE_2D);
240       return False;
241     }
242
243   image = xpm_file_to_ximage (dpy, visual, cmap, filename);
244
245   clear_gl_error();
246   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
247                 image->width, image->height, 0,
248                 GL_RGBA,
249                 /* GL_UNSIGNED_BYTE, */
250                 GL_UNSIGNED_INT_8_8_8_8_REV,
251                 image->data);
252   sprintf (buf, "texture: %.100s (%dx%d)",
253            filename, image->width, image->height);
254   check_gl_error(buf);
255
256   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
257   glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
258   glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
259   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
260   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
261   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
262   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
263
264   glEnable(GL_TEXTURE_GEN_S);
265   glEnable(GL_TEXTURE_GEN_T);
266   glEnable(GL_TEXTURE_2D);
267   return True;
268 }
269
270
271 ENTRYPOINT void
272 init_cow (ModeInfo *mi)
273 {
274   cow_configuration *bp;
275   int wire = MI_IS_WIREFRAME(mi);
276   int i;
277
278   if (!bps) {
279     bps = (cow_configuration *)
280       calloc (MI_NUM_SCREENS(mi), sizeof (cow_configuration));
281     if (!bps) {
282       fprintf(stderr, "%s: out of memory\n", progname);
283       exit(1);
284     }
285
286     bp = &bps[MI_SCREEN(mi)];
287   }
288
289   bp = &bps[MI_SCREEN(mi)];
290
291   bp->glx_context = init_GL(mi);
292
293   reshape_cow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
294
295   glShadeModel(GL_SMOOTH);
296
297   glEnable(GL_DEPTH_TEST);
298   glEnable(GL_NORMALIZE);
299   glEnable(GL_CULL_FACE);
300
301   if (!wire)
302     {
303       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
304 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
305       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
306       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
307       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
308
309       glEnable(GL_LIGHTING);
310       glEnable(GL_LIGHT0);
311       glEnable(GL_DEPTH_TEST);
312       glEnable(GL_CULL_FACE);
313
314       glLightfv(GL_LIGHT0, GL_POSITION, pos);
315       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
316       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
317       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
318     }
319
320   bp->trackball = gltrackball_init ();
321
322   bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
323   for (i = 0; i < countof(all_objs); i++)
324     bp->dlists[i] = glGenLists (1);
325
326   for (i = 0; i < countof(all_objs); i++)
327     {
328       GLfloat black[4] = {0, 0, 0, 1};
329       const struct gllist *gll = *all_objs[i];
330
331       glNewList (bp->dlists[i], GL_COMPILE);
332
333       glMatrixMode(GL_MODELVIEW);
334       glPushMatrix();
335       glMatrixMode(GL_TEXTURE);
336       glPushMatrix();
337       glMatrixMode(GL_MODELVIEW);
338
339       glBindTexture (GL_TEXTURE_2D, 0);
340
341       if (i == HIDE)
342         {
343           GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
344
345           if (load_texture (mi, do_texture))
346             {
347               glBindTexture (GL_TEXTURE_2D, bp->texture);
348               glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
349               glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
350               glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
351
352               /* approximately line it up with ../images/earth.xpm */
353               glMatrixMode(GL_TEXTURE);
354               glTranslatef(0.45, 0.58, 0);
355               glScalef(0.08, 0.16, 1);
356               glRotatef(-5, 0, 0, 1);
357               glMatrixMode(GL_MODELVIEW);
358
359               /* if we have a texture, make the base color be white. */
360               color[0] = color[1] = color[2] = 1.0;
361             }
362
363           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
364           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
365           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
366         }
367       else if (i == TAIL)
368         {
369           GLfloat color[4] = {0.63, 0.43, 0.36, 1.00};
370           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
371           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
372           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
373         }
374       else if (i == UDDER)
375         {
376           GLfloat color[4] = {1.00, 0.53, 0.53, 1.00};
377           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
378           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            black);
379           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           128);
380         }
381       else if (i == HOOFS || i == HORNS)
382         {
383           GLfloat color[4] = {0.20, 0.20, 0.20, 1.00};
384           GLfloat spec[4]  = {0.30, 0.30, 0.30, 1.00};
385           GLfloat shiny    = 8.0;
386           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
387           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
388           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
389         }
390       else if (i == FACE)
391         {
392           GLfloat color[4] = {0.10, 0.10, 0.10, 1.00};
393           GLfloat spec[4]  = {0.10, 0.10, 0.10, 1.00};
394           GLfloat shiny    = 8.0;
395           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
396           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
397           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
398         }
399       else
400         {
401           GLfloat color[4] = {1.00, 1.00, 1.00, 1.00};
402           GLfloat spec[4]  = {1.00, 1.00, 1.00, 1.00};
403           GLfloat shiny    = 128.0;
404           glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
405           glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,            spec);
406           glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS,           shiny);
407         }
408
409       renderList (gll, wire);
410
411       glMatrixMode(GL_TEXTURE);
412       glPopMatrix();
413       glMatrixMode(GL_MODELVIEW);
414       glPopMatrix();
415
416       glEndList ();
417     }
418
419   bp->nfloaters = MI_COUNT (mi);
420   bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
421
422   for (i = 0; i < bp->nfloaters; i++)
423     {
424       floater *f = &bp->floaters[i];
425       f->rot = make_rotator (10.0, 0, 0,
426                              4, 0.05 * speed,
427                              True);
428       if (bp->nfloaters == 2)
429         {
430           f->x = (i ? 6 : -6);
431         }
432       else if (i != 0)
433         {
434           double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
435           double r = 10;
436           f->x = r * cos(th);
437           f->z = r * sin(th);
438         }
439
440       f->ix = f->x;
441       f->iy = f->y;
442       f->iz = f->z;
443       reset_floater (mi, f);
444     }
445 }
446
447
448 static void
449 draw_floater (ModeInfo *mi, floater *f)
450 {
451   cow_configuration *bp = &bps[MI_SCREEN(mi)];
452   GLfloat n;
453   double x, y, z;
454
455   get_position (f->rot, &x, &y, &z, !bp->button_down_p);
456
457   glPushMatrix();
458   glTranslatef (f->x, f->y, f->z);
459
460   glRotatef (y * 360, 0.0, 1.0, 0.0);
461   if (f->spinner_p)
462     {
463       glRotatef (x * 360, 1.0, 0.0, 0.0);
464       glRotatef (z * 360, 0.0, 0.0, 1.0);
465     }
466
467   n = 1.5;
468   if      (bp->nfloaters > 99) n *= 0.05;
469   else if (bp->nfloaters > 25) n *= 0.18;
470   else if (bp->nfloaters > 9)  n *= 0.3;
471   else if (bp->nfloaters > 1)  n *= 0.7;
472   glScalef(n, n, n);
473
474   glCallList (bp->dlists[FACE]);
475   mi->polygon_count += (*all_objs[FACE])->points / 3;
476
477   glCallList (bp->dlists[HIDE]);
478   mi->polygon_count += (*all_objs[HIDE])->points / 3;
479
480   glCallList (bp->dlists[HOOFS]);
481   mi->polygon_count += (*all_objs[HOOFS])->points / 3;
482
483   glCallList (bp->dlists[HORNS]);
484   mi->polygon_count += (*all_objs[HORNS])->points / 3;
485
486   glCallList (bp->dlists[TAIL]);
487   mi->polygon_count += (*all_objs[TAIL])->points / 3;
488
489   glCallList (bp->dlists[UDDER]);
490   mi->polygon_count += (*all_objs[UDDER])->points / 3;
491
492   glPopMatrix();
493 }
494
495
496
497 ENTRYPOINT void
498 draw_cow (ModeInfo *mi)
499 {
500   cow_configuration *bp = &bps[MI_SCREEN(mi)];
501   Display *dpy = MI_DISPLAY(mi);
502   Window window = MI_WINDOW(mi);
503   int i;
504
505   if (!bp->glx_context)
506     return;
507
508   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
509
510   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
511
512   glPushMatrix ();
513   gltrackball_rotate (bp->trackball);
514
515   glScalef (0.5, 0.5, 0.5);
516
517   mi->polygon_count = 0;
518
519 # if 0
520   {
521     floater F;
522     F.x = F.y = F.z = 0;
523     F.dx = F.dy = F.dz = 0;
524     F.ddx = F.ddy = F.ddz = 0;
525     F.rot = make_rotator (0, 0, 0, 1, 0, False);
526     glScalef(2,2,2);
527     draw_floater (mi, &F);
528   }
529 # else
530   for (i = 0; i < bp->nfloaters; i++)
531     {
532       /* "Don't kid yourself, Jimmy.  If a cow ever got the chance,
533          he'd eat you and everyone you care about!"
534              -- Troy McClure in "Meat and You: Partners in Freedom"
535        */
536       floater *f = &bp->floaters[i];
537       draw_floater (mi, f);
538       tick_floater (mi, f);
539     }
540 # endif
541
542   glPopMatrix ();
543
544   if (mi->fps_p) do_fps (mi);
545   glFinish();
546
547   glXSwapBuffers(dpy, window);
548 }
549
550 XSCREENSAVER_MODULE_2 ("BouncingCow", bouncingcow, cow)
551
552 #endif /* USE_GL */