d0c84ed083a67cb7ba459db2633e729bbd5be9ca
[xscreensaver] / hacks / glx / companion.c
1 /* companioncube, Copyright (c) 2011 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
12 /* The symptoms most commonly produced by Enrichment Center testing are
13    superstition, perceiving inanimate objects as alive, and hallucinations.
14    The Enrichment Center reminds you that the weighted companion cube will
15    never threaten to stab you and, in fact, cannot speak.  In the event that
16    the Weighted Companion Cube does speak, the Enrichment Center urges you to
17    disregard its advice.
18  */
19
20
21 #define DEFAULTS        "*delay:        30000       \n" \
22                         "*count:        1           \n" \
23                         "*showFPS:      False       \n" \
24                         "*count:        3           \n" \
25                         "*wireframe:    False       \n" \
26
27 /* #define DEBUG */
28
29
30 # define refresh_cube 0
31 # define release_cube 0
32 #define DEF_SPEED  "1.0"
33 #define DEF_SPIN   "False"
34 #define DEF_WANDER "False"
35
36 #undef countof
37 #define countof(x) (sizeof((x))/sizeof((*x)))
38
39 #undef BELLRAND
40 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
41 #undef RANDSIGN
42 #define RANDSIGN() ((random() & 1) ? 1 : -1)
43
44 #include "xlockmore.h"
45 #include "rotator.h"
46 #include "gltrackball.h"
47 #include "xpm-ximage.h"
48 #include <ctype.h>
49
50 #ifdef USE_GL /* whole file */
51
52 #include "gllist.h"
53
54 extern const struct gllist *companion_quad, *companion_disc, *companion_heart;
55 static const struct gllist * const *all_objs[] = {
56   &companion_quad, &companion_disc, &companion_heart
57 };
58 #define BASE_QUAD  0
59 #define BASE_DISC  1
60 #define BASE_HEART 2
61 #define FULL_CUBE  3
62
63 #define SPEED_SCALE 0.2
64
65 typedef struct {
66   GLfloat x, y, z;
67   GLfloat ix, iy, iz;
68   GLfloat dx, dy, dz;
69   GLfloat ddx, ddy, ddz;
70   GLfloat zr;
71   rotator *rot;
72   Bool spinner_p;
73 } floater;
74
75 typedef struct {
76   GLXContext *glx_context;
77   trackball_state *trackball;
78   Bool button_down_p;
79
80   GLuint *dlists;
81   int cube_polys;
82
83   int nfloaters;
84   floater *floaters;
85
86 } cube_configuration;
87
88 static cube_configuration *bps = NULL;
89
90 static GLfloat speed;
91 static Bool do_spin;
92 static Bool do_wander;
93
94 static XrmOptionDescRec opts[] = {
95   { "-speed",      ".speed",    XrmoptionSepArg, 0       },
96   { "-spin",       ".spin",     XrmoptionNoArg,  "True"  },
97   { "+spin",       ".spin",     XrmoptionNoArg,  "False" },
98   { "-wander",     ".wander",   XrmoptionNoArg,  "True"  },
99   { "+wander",     ".wander",   XrmoptionNoArg,  "False" },
100 };
101
102 static argtype vars[] = {
103   {&speed,     "speed",    "Speed",   DEF_SPEED,    t_Float},
104   {&do_spin,   "spin",     "Spin",    DEF_SPIN,     t_Bool},
105   {&do_wander, "wander",   "Wander",  DEF_WANDER,   t_Bool},
106 };
107
108 ENTRYPOINT ModeSpecOpt cube_opts = {countof(opts), opts, countof(vars), vars, NULL};
109
110
111 #define BOTTOM 28.0
112
113 static void
114 reset_floater (ModeInfo *mi, floater *f)
115 {
116   cube_configuration *bp = &bps[MI_SCREEN(mi)];
117
118   f->y = -BOTTOM;
119   f->x = f->ix;
120   f->z = f->iz;
121
122   /* Yes, I know I'm varying the force of gravity instead of varying the
123      launch velocity.  That's intentional: empirical studies indicate
124      that it's way, way funnier that way. */
125
126   f->dy = 5.0;
127   f->dx = 0;
128   f->dz = 0;
129
130   /* -0.18 max  -0.3 top -0.4 middle  -0.6 bottom */
131   f->ddy = speed * SPEED_SCALE * (-0.6 + BELLRAND(0.45));
132   f->ddx = 0;
133   f->ddz = 0;
134
135   if (do_spin || do_wander)
136     f->spinner_p = 0;
137   else
138     f->spinner_p = !(random() % (3 * bp->nfloaters));
139
140   if (! (random() % (30 * bp->nfloaters)))
141     {
142       f->dx = BELLRAND(1.8) * RANDSIGN();
143       f->dz = BELLRAND(1.8) * RANDSIGN();
144     }
145
146   f->zr = frand(180);
147   if (do_spin || do_wander)
148     {
149       f->y = 0;
150       if (bp->nfloaters > 2)
151         f->y += frand(3.0) * RANDSIGN();
152     }
153 }
154
155
156 static void
157 tick_floater (ModeInfo *mi, floater *f)
158 {
159   cube_configuration *bp = &bps[MI_SCREEN(mi)];
160
161   if (bp->button_down_p) return;
162
163   if (do_spin || do_wander) return;
164
165   f->dx += f->ddx;
166   f->dy += f->ddy;
167   f->dz += f->ddz;
168
169   f->x += f->dx * speed * SPEED_SCALE;
170   f->y += f->dy * speed * SPEED_SCALE;
171   f->z += f->dz * speed * SPEED_SCALE;
172
173   if (f->y < -BOTTOM ||
174       f->x < -BOTTOM*8 || f->x > BOTTOM*8 ||
175       f->z < -BOTTOM*8 || f->z > BOTTOM*8)
176     reset_floater (mi, f);
177 }
178
179
180
181
182
183 static int
184 build_corner (ModeInfo *mi)
185 {
186   cube_configuration *bp = &bps[MI_SCREEN(mi)];
187   GLfloat s;
188   const struct gllist *gll = *all_objs[BASE_QUAD];
189
190   glPushMatrix();
191   glTranslatef (-0.5, -0.5, -0.5);
192   s = 0.659;
193   glScalef (s, s, s);
194
195   glRotatef (180, 0, 1, 0);
196   glRotatef (180, 0, 0, 1);
197   glTranslatef (-0.12, -1.64, 0.12);
198   glCallList (bp->dlists[BASE_QUAD]);
199   glPopMatrix();
200
201   return gll->points / 3;
202 }
203
204
205 static int
206 build_face (ModeInfo *mi)
207 {
208   int polys = 0;
209   cube_configuration *bp = &bps[MI_SCREEN(mi)];
210   int wire = MI_IS_WIREFRAME(mi);
211   GLfloat s;
212   const struct gllist *gll;
213
214   GLfloat base_color[4]   = {0.53, 0.60, 0.66, 1.00};
215   GLfloat heart_color[4]  = {0.92, 0.67, 1.00, 1.00};
216   GLfloat disc_color[4]   = {0.75, 0.92, 1.00, 1.00};
217   GLfloat corner_color[4] = {0.75, 0.92, 1.00, 1.00};
218
219   if (!wire) 
220     {
221       GLfloat w = 0.010;
222       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, base_color);
223       glPushMatrix();
224       glNormal3f (0, 0, -1);
225       glTranslatef (-0.5, -0.5, -0.5);
226
227       glBegin(GL_QUADS);
228       glVertex3f (0,     0,     0);
229       glVertex3f (0,     0.5-w, 0);
230       glVertex3f (0.5-w, 0.5-w, 0);
231       glVertex3f (0.5-w, 0,     0);
232
233       glVertex3f (0.5+w, 0,     0);
234       glVertex3f (0.5+w, 0.5-w, 0);
235       glVertex3f (1,     0.5-w, 0);
236       glVertex3f (1,     0,     0);
237
238       glVertex3f (0,     0.5+w, 0);
239       glVertex3f (0,     1,     0);
240       glVertex3f (0.5-w, 1,     0);
241       glVertex3f (0.5-w, 0.5+w, 0);
242
243       glVertex3f (0.5+w, 0.5+w, 0);
244       glVertex3f (0.5+w, 1,     0);
245       glVertex3f (1,     1,     0);
246       glVertex3f (1,     0.5+w, 0);
247       glEnd();
248
249       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, heart_color);
250
251       glNormal3f (0, -1, 0);
252       glBegin(GL_QUADS);
253       glVertex3f (0, 0.5+w, 0);
254       glVertex3f (1, 0.5+w, 0);
255       glVertex3f (1, 0.5+w, w);
256       glVertex3f (0, 0.5+w, w);
257       glEnd();
258
259       glNormal3f (0, 1, 0);
260       glBegin(GL_QUADS);
261       glVertex3f (0, 0.5-w, w);
262       glVertex3f (1, 0.5-w, w);
263       glVertex3f (1, 0.5-w, 0);
264       glVertex3f (0, 0.5-w, 0);
265       glEnd();
266
267       glNormal3f (-1, 0, 0);
268       glBegin(GL_QUADS);
269       glVertex3f (0.5+w, 0, w);
270       glVertex3f (0.5+w, 1, w);
271       glVertex3f (0.5+w, 1, 0);
272       glVertex3f (0.5+w, 0, 0);
273       glEnd();
274
275       glNormal3f (1, 0, 0);
276       glBegin(GL_QUADS);
277       glVertex3f (0.5-w, 0, 0);
278       glVertex3f (0.5-w, 1, 0);
279       glVertex3f (0.5-w, 1, w);
280       glVertex3f (0.5-w, 0, w);
281       glEnd();
282
283       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, heart_color);
284
285       glNormal3f (0, 0, -1);
286       glTranslatef (0, 0, w);
287       glBegin(GL_QUADS);
288       glVertex3f (0, 0, 0);
289       glVertex3f (0, 1, 0);
290       glVertex3f (1, 1, 0);
291       glVertex3f (1, 0, 0);
292       glEnd();
293
294       glPopMatrix();
295     }
296
297   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, corner_color);
298
299   glPushMatrix();
300   polys += build_corner (mi); glRotatef (90, 0, 0, 1);
301   polys += build_corner (mi); glRotatef (90, 0, 0, 1);
302   polys += build_corner (mi); glRotatef (90, 0, 0, 1);
303   polys += build_corner (mi);
304
305   glRotatef (90, 0, 0, 1);
306   glTranslatef (0.585, -0.585, -0.5655);
307
308   s = 10.5;
309   glScalef (s, s, s);
310   glRotatef (180, 0, 1, 0);
311
312   if (! wire)
313     {
314       gll = *all_objs[BASE_HEART];
315       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, heart_color);
316       glCallList (bp->dlists[BASE_HEART]);
317       polys += gll->points / 3;
318     }
319
320   gll = *all_objs[BASE_DISC];
321   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, disc_color);
322   glCallList (bp->dlists[BASE_DISC]);
323   polys += gll->points / 3;
324
325   glPopMatrix();
326   return polys;
327 }
328
329
330 static int
331 build_cube (ModeInfo *mi)
332 {
333   int polys = 0;
334   glPushMatrix();
335   polys += build_face (mi); glRotatef (90, 0, 1, 0);
336   polys += build_face (mi); glRotatef (90, 0, 1, 0);
337   polys += build_face (mi); glRotatef (90, 0, 1, 0);
338   polys += build_face (mi); glRotatef (90, 1, 0, 0);
339   polys += build_face (mi); glRotatef (180,1, 0, 0);
340   polys += build_face (mi);
341   glPopMatrix();
342   return polys;
343 }
344
345
346 /* Window management, etc
347  */
348 ENTRYPOINT void
349 reshape_cube (ModeInfo *mi, int width, int height)
350 {
351   GLfloat h = (GLfloat) height / (GLfloat) width;
352
353   glViewport (0, 0, (GLint) width, (GLint) height);
354
355   glMatrixMode(GL_PROJECTION);
356   glLoadIdentity();
357   gluPerspective (30.0, 1/h, 1.0, 100);
358
359   glMatrixMode(GL_MODELVIEW);
360   glLoadIdentity();
361   gluLookAt( 0.0, 0.0, 30.0,
362              0.0, 0.0, 0.0,
363              0.0, 1.0, 0.0);
364
365   glClear(GL_COLOR_BUFFER_BIT);
366 }
367
368
369 ENTRYPOINT Bool
370 cube_handle_event (ModeInfo *mi, XEvent *event)
371 {
372   cube_configuration *bp = &bps[MI_SCREEN(mi)];
373
374   if (event->xany.type == ButtonPress &&
375       event->xbutton.button == Button1)
376     {
377       bp->button_down_p = True;
378       gltrackball_start (bp->trackball,
379                          event->xbutton.x, event->xbutton.y,
380                          MI_WIDTH (mi), MI_HEIGHT (mi));
381       return True;
382     }
383   else if (event->xany.type == ButtonRelease &&
384            event->xbutton.button == Button1)
385     {
386       bp->button_down_p = False;
387       return True;
388     }
389   else if (event->xany.type == ButtonPress &&
390            (event->xbutton.button == Button4 ||
391             event->xbutton.button == Button5 ||
392             event->xbutton.button == Button6 ||
393             event->xbutton.button == Button7))
394     {
395       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 3,
396                               !event->xbutton.state);
397       return True;
398     }
399   else if (event->xany.type == MotionNotify &&
400            bp->button_down_p)
401     {
402       gltrackball_track (bp->trackball,
403                          event->xmotion.x, event->xmotion.y,
404                          MI_WIDTH (mi), MI_HEIGHT (mi));
405       return True;
406     }
407
408   return False;
409 }
410
411
412 ENTRYPOINT void
413 init_cube (ModeInfo *mi)
414 {
415   cube_configuration *bp;
416   int wire = MI_IS_WIREFRAME(mi);
417   int i;
418
419   if (!bps) {
420     bps = (cube_configuration *)
421       calloc (MI_NUM_SCREENS(mi), sizeof (cube_configuration));
422     if (!bps) {
423       fprintf(stderr, "%s: out of memory\n", progname);
424       exit(1);
425     }
426   }
427
428   bp = &bps[MI_SCREEN(mi)];
429
430   bp->glx_context = init_GL(mi);
431
432   reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
433
434   glShadeModel(GL_SMOOTH);
435
436   glEnable(GL_DEPTH_TEST);
437   glEnable(GL_NORMALIZE);
438   glEnable(GL_CULL_FACE);
439
440   if (!wire)
441     {
442       GLfloat pos[4] = {0.7, 0.2, 0.4, 0.0};
443 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
444       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
445       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
446       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
447
448       glEnable(GL_LIGHTING);
449       glEnable(GL_LIGHT0);
450       glEnable(GL_DEPTH_TEST);
451       glEnable(GL_CULL_FACE);
452
453       glLightfv(GL_LIGHT0, GL_POSITION, pos);
454       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
455       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
456       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
457     }
458
459   bp->trackball = gltrackball_init ();
460
461   bp->dlists = (GLuint *) calloc (countof(all_objs)+2, sizeof(GLuint));
462   for (i = 0; i < countof(all_objs)+1; i++)
463     bp->dlists[i] = glGenLists (1);
464
465   for (i = 0; i < countof(all_objs); i++)
466     {
467       const struct gllist *gll = *all_objs[i];
468       glNewList (bp->dlists[i], GL_COMPILE);
469       renderList (gll, wire);
470       glEndList ();
471     }
472
473   glNewList (bp->dlists[i], GL_COMPILE);
474   bp->cube_polys = build_cube (mi);
475   glEndList ();
476
477
478   bp->nfloaters = MI_COUNT (mi);
479   bp->floaters = (floater *) calloc (bp->nfloaters, sizeof (floater));
480
481   for (i = 0; i < bp->nfloaters; i++)
482     {
483       floater *f = &bp->floaters[i];
484       double spin_speed   = do_spin ? 0.7 : 10;
485       double wander_speed = do_wander ? 0.02 : 0.05 * speed * SPEED_SCALE;
486       double spin_accel   = 0.5;
487       f->rot = make_rotator (spin_speed, spin_speed, spin_speed,
488                              spin_accel,
489                              wander_speed,
490                              True);
491       if (bp->nfloaters == 2)
492         {
493           f->x = (i ? 2 : -2);
494         }
495       else if (i != 0)
496         {
497           double th = (i - 1) * M_PI*2 / (bp->nfloaters-1);
498           double r = 3;
499           f->x = r * cos(th);
500           f->z = r * sin(th);
501         }
502
503       f->ix = f->x;
504       f->iy = f->y;
505       f->iz = f->z;
506       reset_floater (mi, f);
507     }
508 }
509
510
511 static void
512 draw_floater (ModeInfo *mi, floater *f)
513 {
514   cube_configuration *bp = &bps[MI_SCREEN(mi)];
515   GLfloat n;
516   double x, y, z;
517
518   get_position (f->rot, &x, &y, &z, !bp->button_down_p);
519
520   glPushMatrix();
521   glTranslatef (f->x, f->y, f->z);
522
523   if (do_wander)
524     glTranslatef (x, y, z);
525
526   if (do_spin)
527     get_rotation (f->rot, &x, &y, &z, !bp->button_down_p);
528
529   if (do_spin || f->spinner_p)
530     {
531       glRotatef (x * 360, 1, 0, 0);
532       glRotatef (y * 360, 0, 1, 0);
533       glRotatef (z * 360, 0, 0, 1);
534     }
535   else
536     {
537       glRotatef (f->zr * 360, 0, 1, 0);
538     }
539
540   n = 1.5;
541   if      (bp->nfloaters > 99) n *= 0.05;
542   else if (bp->nfloaters > 25) n *= 0.18;
543   else if (bp->nfloaters > 9)  n *= 0.3;
544   else if (bp->nfloaters > 1)  n *= 0.7;
545
546   n *= 2;
547
548   if ((do_spin || do_wander) && bp->nfloaters > 1)
549     n *= 0.7;
550
551   glScalef(n, n, n);
552
553   glCallList (bp->dlists[FULL_CUBE]);
554   mi->polygon_count += bp->cube_polys;
555 /*  build_cube (mi);*/
556
557   glPopMatrix();
558 }
559
560
561
562 ENTRYPOINT void
563 draw_cube (ModeInfo *mi)
564 {
565   cube_configuration *bp = &bps[MI_SCREEN(mi)];
566   Display *dpy = MI_DISPLAY(mi);
567   Window window = MI_WINDOW(mi);
568   int i;
569
570   if (!bp->glx_context)
571     return;
572
573   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
574
575   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
576
577   glPushMatrix ();
578   glRotatef(current_device_rotation(), 0, 0, 1);
579   gltrackball_rotate (bp->trackball);
580
581   glScalef (2, 2, 2);
582
583   mi->polygon_count = 0;
584
585 # if 0
586   {
587     floater F;
588     F.x = F.y = F.z = 0;
589     F.dx = F.dy = F.dz = 0;
590     F.ddx = F.ddy = F.ddz = 0;
591     F.rot = make_rotator (0, 0, 0, 1, 0, False);
592     glRotatef (45, 0, 1, 0);
593     draw_floater (mi, &F);
594   }
595 # else
596   for (i = 0; i < bp->nfloaters; i++)
597     {
598       floater *f = &bp->floaters[i];
599       draw_floater (mi, f);
600       tick_floater (mi, f);
601     }
602 # endif
603
604   glPopMatrix ();
605
606   if (mi->fps_p) do_fps (mi);
607   glFinish();
608
609   glXSwapBuffers(dpy, window);
610 }
611
612 XSCREENSAVER_MODULE_2 ("CompanionCube", companioncube, cube)
613
614 #endif /* USE_GL */