From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / moebiusgears.c
1 /* moebiusgears, Copyright (c) 2007-2014 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 #define DEFAULTS        "*delay:        30000       \n" \
13                         "*count:        17          \n" \
14                         "*showFPS:      False       \n" \
15                         "*wireframe:    False       \n" \
16                         "*suppressRotationAnimation: True\n" \
17
18 # define refresh_mgears 0
19 # define release_mgears 0
20 #undef countof
21 #define countof(x) (sizeof((x))/sizeof((*x)))
22
23 #include "xlockmore.h"
24 #include "involute.h"
25 #include "normals.h"
26 #include "rotator.h"
27 #include "gltrackball.h"
28 #include <ctype.h>
29
30 #ifdef USE_GL /* whole file */
31
32
33 #define DEF_SPIN        "True"
34 #define DEF_WANDER      "True"
35 #define DEF_ROLL        "True"
36 #define DEF_SPEED       "1.0"
37 #define DEF_TEETH       "15"
38
39 typedef struct {
40
41   gear g;
42   GLfloat pos_th;  /* position on ring of gear system */
43   GLfloat pos_thz; /* rotation out of plane of gear system */
44 } mogear;
45
46 typedef struct {
47   GLXContext *glx_context;
48   rotator *rot;
49   trackball_state *trackball;
50   Bool button_down_p;
51
52   int ngears;
53   mogear *gears;
54   GLfloat ring_r;  /* radius of gear system */
55   GLfloat roll_th;
56
57 } mgears_configuration;
58
59 static mgears_configuration *bps = NULL;
60
61 static Bool do_spin;
62 static GLfloat speed;
63 static Bool do_wander;
64 static Bool do_roll;
65 static int teeth_arg;
66
67 static XrmOptionDescRec opts[] = {
68   { "-spin",   ".spin",   XrmoptionNoArg, "True"  },
69   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
70   { "-speed",  ".speed",  XrmoptionSepArg, 0      },
71   { "-wander", ".wander", XrmoptionNoArg, "True"  },
72   { "+wander", ".wander", XrmoptionNoArg, "False" },
73   { "-roll",   ".roll",   XrmoptionNoArg, "True"  },
74   { "+roll",   ".roll",   XrmoptionNoArg, "False" },
75   { "-teeth",  ".teeth",  XrmoptionSepArg, 0      },
76 };
77
78 static argtype vars[] = {
79   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
80   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
81   {&do_roll,   "roll",   "Roll",   DEF_ROLL,   t_Bool},
82   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
83   {&teeth_arg, "teeth",  "Teeth",  DEF_TEETH,  t_Int},
84 };
85
86 ENTRYPOINT ModeSpecOpt mgears_opts = {countof(opts), opts, countof(vars), vars, NULL};
87
88
89 /* Window management, etc
90  */
91 ENTRYPOINT void
92 reshape_mgears (ModeInfo *mi, int width, int height)
93 {
94   GLfloat h = (GLfloat) height / (GLfloat) width;
95
96   glViewport (0, 0, (GLint) width, (GLint) height);
97
98   glMatrixMode(GL_PROJECTION);
99   glLoadIdentity();
100   gluPerspective (30.0, 1/h, 1.0, 100.0);
101
102   glMatrixMode(GL_MODELVIEW);
103   glLoadIdentity();
104   gluLookAt( 0.0, 0.0, 30.0,
105              0.0, 0.0, 0.0,
106              0.0, 1.0, 0.0);
107
108 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
109   {
110     int o = (int) current_device_rotation();
111     if (o != 0 && o != 180 && o != -180)
112       glScalef (1/h, 1/h, 1/h);
113   }
114 # endif
115
116   glClear(GL_COLOR_BUFFER_BIT);
117 }
118
119
120 static void
121 reset_mgears (ModeInfo *mi)
122 {
123   mgears_configuration *bp = &bps[MI_SCREEN(mi)];
124   int wire = MI_IS_WIREFRAME(mi);
125   int total_gears = MI_COUNT(mi);
126   double gears_per_turn;
127   double gear_r, tw, th, thick, slope;
128   int i, nubs, size;
129
130   if (! (total_gears & 1)) 
131     total_gears++;              /* must be odd or gears intersect */
132
133   /* Number of teeth must be odd if number of gears is odd, or teeth don't
134      mesh when the loop closes.  And since number of gears must be odd...
135   */
136   if (! (teeth_arg & 1)) teeth_arg++;
137   if (teeth_arg < 7) teeth_arg = 7;
138
139   if (total_gears < 13) /* gear mesh angle is too steep with less */
140     total_gears = 13;
141
142   thick = 0.2;
143   nubs = (random() & 3) ? 0 : (random() % teeth_arg) / 2;
144
145   slope = 0;
146
147   /* Sloping gears are incompatible with "-roll" ... */
148   /* slope= -M_PI * 2 / total_gears; */
149
150   gears_per_turn = total_gears / 2.0;
151
152   bp->ring_r = 3;
153   gear_r = M_PI * bp->ring_r / gears_per_turn;
154   tw = 0;
155   th = gear_r * 2.5 / teeth_arg;
156
157   /* If the gears are small, use a lower density mesh. */
158   size = (gear_r > 0.60 ? INVOLUTE_HUGE   :
159           gear_r > 0.32 ? INVOLUTE_LARGE  :
160           gear_r > 0.13 ? INVOLUTE_MEDIUM :
161           INVOLUTE_SMALL);
162
163   /* If there are lots of teeth, use a lower density mesh. */
164   if (teeth_arg > 77)
165     size = INVOLUTE_SMALL;
166   if (teeth_arg > 45 && size >= INVOLUTE_HUGE)
167     size = INVOLUTE_MEDIUM;
168
169   if (bp->gears)
170     {
171       for (i = 0; i < bp->ngears; i++)
172         glDeleteLists (bp->gears[i].g.dlist, 1);
173       free (bp->gears);
174       bp->gears = 0;
175     }
176
177   bp->ngears = total_gears;
178
179   bp->gears = (mogear *) calloc (bp->ngears, sizeof(*bp->gears));
180   for (i = 0; i < bp->ngears; i++)
181     {
182       mogear *mg = &bp->gears[i];
183       gear *g = &mg->g;
184
185       g->r           = gear_r;
186       g->size        = size;
187       g->nteeth      = teeth_arg;
188       g->tooth_w     = tw;
189       g->tooth_h     = th;
190       g->tooth_slope = slope;
191       g->thickness   = g->r * thick;
192       g->thickness2  = g->thickness * 0.1;
193       g->thickness3  = g->thickness;
194       g->inner_r     = g->r * 0.80;
195       g->inner_r2    = g->r * 0.60;
196       g->inner_r3    = g->r * 0.55;
197       g->nubs        = nubs;
198       mg->pos_th     = (M_PI * 2 / gears_per_turn) * i;
199       mg->pos_thz    = (M_PI / 2 / gears_per_turn) * i;
200
201       g->th = ((i & 1)
202                ? (M_PI * 2 / g->nteeth)
203                : 0);
204
205       /* Colorize
206        */
207       g->color[0] = 0.7 + frand(0.3);
208       g->color[1] = 0.7 + frand(0.3);
209       g->color[2] = 0.7 + frand(0.3);
210       g->color[3] = 1.0;
211
212       g->color2[0] = g->color[0] * 0.85;
213       g->color2[1] = g->color[1] * 0.85;
214       g->color2[2] = g->color[2] * 0.85;
215       g->color2[3] = g->color[3];
216
217       /* Now render the gear into its display list.
218        */
219       g->dlist = glGenLists (1);
220       if (! g->dlist)
221         {
222           check_gl_error ("glGenLists");
223           abort();
224         }
225
226       glNewList (g->dlist, GL_COMPILE);
227       g->polygons += draw_involute_gear (g, wire);
228       glEndList ();
229     }
230 }
231
232
233 ENTRYPOINT void 
234 init_mgears (ModeInfo *mi)
235 {
236   mgears_configuration *bp;
237   int wire = MI_IS_WIREFRAME(mi);
238
239   if (!bps) {
240     bps = (mgears_configuration *)
241       calloc (MI_NUM_SCREENS(mi), sizeof (mgears_configuration));
242     if (!bps) {
243       fprintf(stderr, "%s: out of memory\n", progname);
244       exit(1);
245     }
246   }
247
248   bp = &bps[MI_SCREEN(mi)];
249
250   bp->glx_context = init_GL(mi);
251
252   reshape_mgears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
253
254   if (!wire)
255     {
256       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
257       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
258       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
259       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
260
261       glEnable(GL_LIGHTING);
262       glEnable(GL_LIGHT0);
263       glEnable(GL_DEPTH_TEST);
264       glEnable(GL_CULL_FACE);
265
266       glLightfv(GL_LIGHT0, GL_POSITION, pos);
267       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
268       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
269       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
270     }
271
272   {
273     double spin_speed   = 0.5;
274     double wander_speed = 0.01;
275     double spin_accel   = 2.0;
276
277     bp->rot = make_rotator (do_spin ? spin_speed : 0,
278                             do_spin ? spin_speed : 0,
279                             do_spin ? spin_speed : 0,
280                             spin_accel,
281                             do_wander ? wander_speed : 0,
282                             False /* don't randomize */
283                             );
284     bp->trackball = gltrackball_init (True);
285   }
286
287   reset_mgears (mi);
288 }
289
290
291 ENTRYPOINT Bool
292 mgears_handle_event (ModeInfo *mi, XEvent *event)
293 {
294   mgears_configuration *bp = &bps[MI_SCREEN(mi)];
295
296   if (gltrackball_event_handler (event, bp->trackball,
297                                  MI_WIDTH (mi), MI_HEIGHT (mi),
298                                  &bp->button_down_p))
299     return True;
300   else if (event->xany.type == KeyPress)
301     {
302       KeySym keysym;
303       char c = 0;
304       XLookupString (&event->xkey, &c, 1, &keysym, 0);
305       if (c == '+' || c == '=' ||
306           keysym == XK_Up || keysym == XK_Right || keysym == XK_Next)
307         {
308           MI_COUNT(mi) += 2;
309           reset_mgears (mi);
310           return True;
311         }
312       else if (c == '-' || c == '_' ||
313                keysym == XK_Down || keysym == XK_Left || keysym == XK_Prior)
314         {
315           if (MI_COUNT(mi) <= 13)
316             return False;
317           MI_COUNT(mi) -= 2;
318           reset_mgears (mi);
319           return True;
320         }
321       else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
322         goto DEF;
323     }
324   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
325     {
326     DEF:
327       MI_COUNT(mi) = 13 + (2 * (random() % 10));
328       reset_mgears (mi);
329       return True;
330     }
331
332   return False;
333 }
334
335
336 ENTRYPOINT void
337 draw_mgears (ModeInfo *mi)
338 {
339   mgears_configuration *bp = &bps[MI_SCREEN(mi)];
340   Display *dpy = MI_DISPLAY(mi);
341   Window window = MI_WINDOW(mi);
342   int i;
343
344   if (!bp->glx_context)
345     return;
346
347   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
348
349   glShadeModel(GL_SMOOTH);
350
351   glEnable(GL_DEPTH_TEST);
352   glEnable(GL_NORMALIZE);
353   glEnable(GL_CULL_FACE);
354
355   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
356
357   glPushMatrix ();
358
359   glScalef(1.1, 1.1, 1.1);
360
361   {
362     double x, y, z;
363     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
364     glTranslatef ((x - 0.5) * 4,
365                   (y - 0.5) * 4,
366                   (z - 0.5) * 7);
367
368     gltrackball_rotate (bp->trackball);
369
370     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
371
372     /* add a little rotation for -no-spin mode */
373     x -= 0.14;
374     y -= 0.06;
375
376     glRotatef (x * 360, 1.0, 0.0, 0.0);
377     glRotatef (y * 360, 0.0, 1.0, 0.0);
378     glRotatef (z * 360, 0.0, 0.0, 1.0);
379   }
380
381   mi->polygon_count = 0;
382
383   glScalef (1.5, 1.5, 1.5);
384
385 /*#define DEBUG*/
386
387 #ifdef DEBUG
388   glScalef (.5, .5, .5);
389   glTranslatef (0, -bp->gears[0].g.r * bp->ngears, 0);
390 #endif
391
392   for (i = 0; i < bp->ngears; i++)
393     {
394       mogear *mg = &bp->gears[i];
395       gear *g = &mg->g;
396
397       glPushMatrix();
398 #ifndef DEBUG
399       glRotatef (mg->pos_th  * 180 / M_PI, 0, 0, 1);  /* rotation on ring */
400       glTranslatef (bp->ring_r, 0, 0);                /* position on ring */
401       glRotatef (mg->pos_thz * 180 / M_PI, 0, 1, 0);  /* twist a bit */
402
403       if (do_roll)
404         {
405           glRotatef (bp->roll_th * 180 / M_PI, 0, 1, 0);
406           bp->roll_th += speed * 0.0005;
407         }
408 #else
409       glTranslatef (0, i * 2 * g->r, 0);
410 #endif
411       glRotatef (g->th * 180 / M_PI, 0, 0, 1);
412
413       glCallList (g->dlist);
414       mi->polygon_count += g->polygons;
415       glPopMatrix ();
416     }
417
418   glPopMatrix ();
419
420 #ifndef DEBUG
421   /* spin gears */
422   for (i = 0; i < bp->ngears; i++)
423     {
424       mogear *mg = &bp->gears[i];
425       mg->g.th += speed * (M_PI / 100) * (i & 1 ? 1 : -1);
426     }
427 #endif
428
429   if (mi->fps_p) do_fps (mi);
430   glFinish();
431
432   glXSwapBuffers(dpy, window);
433 }
434
435 XSCREENSAVER_MODULE_2 ("MoebiusGears", moebiusgears, mgears)
436
437 #endif /* USE_GL */