1 /* gears, Copyright (c) 2007 Jamie Zawinski <jwz@jwz.org>
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
11 * Originally written by Brian Paul in 1996 or earlier;
12 * rewritten by jwz in Nov 2007.
15 #define DEFAULTS "*delay: 30000 \n" \
17 "*showFPS: False \n" \
18 "*wireframe: False \n" \
20 # define refresh_gears 0
21 # define release_gears 0
23 #define countof(x) (sizeof((x))/sizeof((*x)))
25 #include "xlockmore.h"
30 #include "gltrackball.h"
33 #ifdef USE_GL /* whole file */
36 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
38 #define DEF_SPIN "True"
39 #define DEF_WANDER "True"
40 #define DEF_SPEED "1.0"
43 GLXContext *glx_context;
45 trackball_state *trackball;
52 GLuint armature_dlist;
53 int armature_polygons;
55 struct { GLfloat x1, y1, x2, y2; } bbox;
57 } gears_configuration;
59 static gears_configuration *bps = NULL;
63 static Bool do_wander;
65 static XrmOptionDescRec opts[] = {
66 { "-spin", ".spin", XrmoptionNoArg, "True" },
67 { "+spin", ".spin", XrmoptionNoArg, "False" },
68 { "-speed", ".speed", XrmoptionSepArg, 0 },
69 { "-wander", ".wander", XrmoptionNoArg, "True" },
70 { "+wander", ".wander", XrmoptionNoArg, "False" },
73 static argtype vars[] = {
74 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
75 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
76 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
79 ENTRYPOINT ModeSpecOpt gears_opts = {countof(opts), opts, countof(vars), vars, NULL};
82 /* Window management, etc
85 reshape_gears (ModeInfo *mi, int width, int height)
87 GLfloat h = (GLfloat) height / (GLfloat) width;
89 glViewport (0, 0, (GLint) width, (GLint) height);
91 glMatrixMode(GL_PROJECTION);
93 gluPerspective (30.0, 1/h, 1.0, 100.0);
95 glMatrixMode(GL_MODELVIEW);
97 gluLookAt( 0.0, 0.0, 30.0,
101 glClear(GL_COLOR_BUFFER_BIT);
106 gears_handle_event (ModeInfo *mi, XEvent *event)
108 gears_configuration *bp = &bps[MI_SCREEN(mi)];
110 if (event->xany.type == ButtonPress &&
111 event->xbutton.button == Button1)
113 bp->button_down_p = True;
114 gltrackball_start (bp->trackball,
115 event->xbutton.x, event->xbutton.y,
116 MI_WIDTH (mi), MI_HEIGHT (mi));
119 else if (event->xany.type == ButtonRelease &&
120 event->xbutton.button == Button1)
122 bp->button_down_p = False;
125 else if (event->xany.type == ButtonPress &&
126 (event->xbutton.button == Button4 ||
127 event->xbutton.button == Button5))
129 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
130 !!event->xbutton.state);
133 else if (event->xany.type == MotionNotify &&
136 gltrackball_track (bp->trackball,
137 event->xmotion.x, event->xmotion.y,
138 MI_WIDTH (mi), MI_HEIGHT (mi));
151 glDeleteLists (g->dlist, 1);
156 /* Create and return a new gear sized for placement next to or on top of
157 the given parent gear (if any.) Returns 0 if out of memory.
158 [Mostly lifted from pinion.c]
161 new_gear (ModeInfo *mi, gear *parent)
163 gears_configuration *bp = &bps[MI_SCREEN(mi)];
164 gear *g = (gear *) calloc (1, sizeof (*g));
165 static unsigned long id = 0; /* only used in debugging output */
170 /* Pick the size of the teeth.
172 if (parent) /* adjascent gears need matching teeth */
174 g->tooth_w = parent->tooth_w;
175 g->tooth_h = parent->tooth_h;
176 g->tooth_slope = -parent->tooth_slope;
178 else /* gears that begin trains get any size they want */
180 g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
181 g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
183 g->tooth_slope = ((random() % 8)
185 : 0.5 + BELLRAND(1));
189 /* Pick the number of teeth, and thus, the radius.
194 if (!parent || bp->ngears > 4)
195 g->nteeth = 5 + BELLRAND (20);
197 g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
199 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
200 g->r = c / (M_PI * 2); /* c = 2 pi r */
203 g->thickness = g->tooth_w + frand (g->r);
204 g->thickness2 = g->thickness * 0.7;
205 g->thickness3 = g->thickness;
209 g->color[0] = 0.5 + frand(0.5);
210 g->color[1] = 0.5 + frand(0.5);
211 g->color[2] = 0.5 + frand(0.5);
214 g->color2[0] = g->color[0] * 0.85;
215 g->color2[1] = g->color[1] * 0.85;
216 g->color2[2] = g->color[2] * 0.85;
217 g->color2[3] = g->color[3];
220 /* Decide on shape of gear interior:
221 - just a ring with teeth;
222 - that, plus a thinner in-set "plate" in the middle;
223 - that, plus a thin raised "lip" on the inner plate;
224 - or, a wide lip (really, a thicker third inner plate.)
226 if ((random() % 10) == 0)
228 /* inner_r can go all the way in; there's no inset disc. */
229 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
235 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
236 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
237 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
240 if (g->inner_r2 > (g->r * 0.2))
242 int nn = (random() % 10);
244 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
245 else if (nn <= 7 && g->inner_r2 >= 0.1)
246 g->inner_r3 = g->inner_r2 - 0.01;
250 /* If we have three discs, sometimes make the middle disc be spokes.
252 if (g->inner_r3 && ((random() % 5) == 0))
254 g->spokes = 2 + BELLRAND (5);
255 g->spoke_thickness = 1 + frand(7.0);
256 if (g->spokes == 2 && g->spoke_thickness < 2)
257 g->spoke_thickness += 1;
260 /* Sometimes add little nubbly bits, if there is room.
265 involute_biggest_ring (g, 0, &size, 0);
266 if (size > g->r * 0.2 && (random() % 5) == 0)
268 g->nubs = 1 + (random() % 16);
269 if (g->nubs > 8) g->nubs = 1;
273 if (g->inner_r3 > g->inner_r2) abort();
274 if (g->inner_r2 > g->inner_r) abort();
275 if (g->inner_r > g->r) abort();
277 /* Decide how complex the polygon model should be.
280 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
281 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
282 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
283 else g->size = INVOLUTE_LARGE;
292 /* Given a newly-created gear, place it next to its parent in the scene,
293 with its teeth meshed and the proper velocity. Returns False if it
294 didn't work. (Call this a bunch of times until either it works, or
295 you decide it's probably not going to.)
296 [Mostly lifted from pinion.c]
299 place_gear (ModeInfo *mi, gear *g, gear *parent)
301 gears_configuration *bp = &bps[MI_SCREEN(mi)];
303 /* Compute this gear's velocity.
307 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
308 g->th = 1; /* not 0 */
312 /* Gearing ratio is the ratio of the number of teeth to previous gear
313 (which is also the ratio of the circumferences.)
315 g->ratio = (double) parent->nteeth / (double) g->nteeth;
317 /* Set our initial rotation to match that of the previous gear,
318 multiplied by the gearing ratio. (This is finessed later,
319 once we know the exact position of the gear relative to its
322 g->th = -(parent->th * g->ratio);
324 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
326 double off = (180.0 / g->nteeth);
333 /* ratios are cumulative for all gears in the train. */
334 g->ratio *= parent->ratio;
338 if (parent) /* Place the gear next to the parent. */
340 double r_off = parent->r + g->r;
343 angle = (random() % 360) - 180; /* -180 to +180 degrees */
345 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
346 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
349 /* avoid accidentally changing sign of "th" in the math below. */
350 g->th += (g->th > 0 ? 360 : -360);
352 /* Adjust the rotation of the gear so that its teeth line up with its
353 parent, based on the position of the gear and the current rotation
357 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
358 double g_c = 2 * M_PI * g->r; /* circumference of g */
360 double p_t = p_c * (angle/360.0); /* distance travelled along
361 circumference of parent when
362 moving "angle" degrees along
364 double g_rat = p_t / g_c; /* if travelling that distance
365 along circumference of g,
366 ratio of g's circumference
368 double g_th = 360.0 * g_rat; /* that ratio in degrees */
370 g->th += angle + g_th;
374 /* If the position we picked for this gear causes it to overlap
375 with any earlier gear in the train, give up.
380 for (i = bp->ngears-1; i >= 0; i--)
382 gear *og = bp->gears[i];
384 if (og == g) continue;
385 if (og == parent) continue;
386 if (g->z != og->z) continue; /* Ignore unless on same layer */
388 /* Collision detection without sqrt:
389 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
390 d < r1 + r2 d^2 < (r1 + r2)^2
392 if (((g->x - og->x) * (g->x - og->x) +
393 (g->y - og->y) * (g->y - og->y)) <
394 ((g->r + g->tooth_h + og->r + og->tooth_h) *
395 (g->r + g->tooth_h + og->r + og->tooth_h)))
404 /* Make a new gear, place it next to its parent in the scene,
405 with its teeth meshed and the proper velocity. Returns the gear;
406 or 0 if it didn't work. (Call this a bunch of times until either
407 it works, or you decide it's probably not going to.)
408 [Mostly lifted from pinion.c]
411 place_new_gear (ModeInfo *mi, gear *parent)
413 gears_configuration *bp = &bps[MI_SCREEN(mi)];
420 if (loop_count >= 100)
428 g = new_gear (mi, parent);
429 if (!g) return 0; /* out of memory? */
431 if (place_gear (mi, g, parent))
437 /* We got a gear, and it is properly positioned.
438 Insert it in the scene.
440 bp->gears[bp->ngears++] = g;
447 GLfloat width1, GLfloat height1,
448 GLfloat width2, GLfloat height2,
452 glShadeModel(GL_FLAT);
454 #if 0 /* don't need these - they're embedded in other objects */
457 glNormal3f(-1, 0, 0);
458 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
459 glVertex3f(-length/2, -width1/2, -height1/2);
460 glVertex3f(-length/2, width1/2, -height1/2);
461 glVertex3f(-length/2, width1/2, height1/2);
462 glVertex3f(-length/2, -width1/2, height1/2);
469 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
470 glVertex3f(length/2, -width2/2, -height2/2);
471 glVertex3f(length/2, width2/2, -height2/2);
472 glVertex3f(length/2, width2/2, height2/2);
473 glVertex3f(length/2, -width2/2, height2/2);
480 glNormal3f(0, 0, -1);
481 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
482 glVertex3f(-length/2, -width1/2, -height1/2);
483 glVertex3f(-length/2, width1/2, -height1/2);
484 glVertex3f( length/2, width2/2, -height2/2);
485 glVertex3f( length/2, -width2/2, -height2/2);
492 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
493 glVertex3f(-length/2, -width1/2, height1/2);
494 glVertex3f(-length/2, width1/2, height1/2);
495 glVertex3f( length/2, width2/2, height2/2);
496 glVertex3f( length/2, -width2/2, height2/2);
502 glNormal3f(0, -1, 0);
503 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
504 glVertex3f(-length/2, -width1/2, -height1/2);
505 glVertex3f(-length/2, -width1/2, height1/2);
506 glVertex3f( length/2, -width2/2, height2/2);
507 glVertex3f( length/2, -width2/2, -height2/2);
514 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
515 glVertex3f(-length/2, width1/2, -height1/2);
516 glVertex3f(-length/2, width1/2, height1/2);
517 glVertex3f( length/2, width2/2, height2/2);
518 glVertex3f( length/2, width2/2, -height2/2);
529 ctube (GLfloat diameter, GLfloat width, Bool wire)
534 32, True, True, wire);
539 armature (ModeInfo *mi)
541 gears_configuration *bp = &bps[MI_SCREEN(mi)];
542 int wire = MI_IS_WIREFRAME(mi);
544 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
545 GLfloat shiny = 128.0;
548 color[0] = 0.5 + frand(0.5);
549 color[1] = 0.5 + frand(0.5);
550 color[2] = 0.5 + frand(0.5);
553 bp->armature_polygons = 0;
555 bp->armature_dlist = glGenLists (1);
556 if (! bp->armature_dlist)
558 check_gl_error ("glGenLists");
562 glNewList (bp->armature_dlist, GL_COMPILE);
564 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
565 glMateriali (GL_FRONT, GL_SHININESS, shiny);
566 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
567 glColor3f (color[0], color[1], color[2]);
572 GLfloat s = bp->gears[0]->r * 2.7;
577 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
578 glRotatef (30, 0, 0, 1);
580 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
583 glTranslatef(0.0, 4.2, -1);
584 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
585 glTranslatef(0, 0, 1.8);
586 bp->armature_polygons += ctube (0.7, 0.7, wire);
590 glRotatef(120, 0.0, 0.0, 1.0);
591 glTranslatef(0.0, 4.2, -1);
592 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
593 glTranslatef(0, 0, 1.8);
594 bp->armature_polygons += ctube (0.7, 0.7, wire);
598 glRotatef(240, 0.0, 0.0, 1.0);
599 glTranslatef(0.0, 4.2, -1);
600 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
601 glTranslatef(0, 0, 1.8);
602 bp->armature_polygons += ctube (0.7, 0.7, wire);
605 glTranslatef(0, 0, 1.5); /* center disk */
606 bp->armature_polygons += ctube (1.5, 2, wire);
609 glRotatef(270, 0, 0, 1);
610 glRotatef(-10, 0, 1, 0);
611 glTranslatef(-2.2, 0, 0);
612 bp->armature_polygons += arm (4.0, 1.0, 0.5,
613 2.0, 1.0, wire); /* arm 1 */
617 glRotatef(30, 0, 0, 1);
618 glRotatef(-10, 0, 1, 0);
619 glTranslatef(-2.2, 0, 0);
620 bp->armature_polygons += arm (4.0, 1.0, 0.5,
621 2.0, 1.0, wire); /* arm 2 */
625 glRotatef(150, 0, 0, 1);
626 glRotatef(-10, 0, 1, 0);
627 glTranslatef(-2.2, 0, 0);
628 bp->armature_polygons += arm (4.0, 1.0, 0.5,
629 2.0, 1.0, wire); /* arm 3 */
639 planetary_gears (ModeInfo *mi)
641 gears_configuration *bp = &bps[MI_SCREEN(mi)];
642 gear *g0, *g1, *g2, *g3, *g4;
643 GLfloat distance = 2.02;
645 bp->planetary_p = True;
647 g0 = new_gear (mi, 0);
648 g1 = new_gear (mi, 0);
649 g2 = new_gear (mi, 0);
650 g3 = new_gear (mi, 0);
651 g4 = new_gear (mi, 0);
653 if (! place_gear (mi, g0, 0)) abort();
654 if (! place_gear (mi, g1, 0)) abort();
655 if (! place_gear (mi, g2, 0)) abort();
656 if (! place_gear (mi, g3, 0)) abort();
657 if (! place_gear (mi, g4, 0)) abort();
659 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
660 g0->tooth_w = g0->r / g0->nteeth;
661 g0->tooth_h = g0->tooth_w * 2.8;
663 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
680 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
681 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
683 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
684 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
686 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
687 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
693 g0->inverted_p = True;
696 g0->nteeth = g4->nteeth * 3;
697 g0->r = g4->r * 3.05;
698 g0->inner_r = g0->r * 0.8;
701 g0->th = -(g4->th - (180 / g0->nteeth));
702 g0->ratio = g4->ratio / 3;
707 g0->size = INVOLUTE_LARGE;
709 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
712 bp->gears[bp->ngears++] = g1;
713 bp->gears[bp->ngears++] = g2;
714 bp->gears[bp->ngears++] = g3;
715 bp->gears[bp->ngears++] = g4;
716 bp->gears[bp->ngears++] = g0;
723 init_gears (ModeInfo *mi)
725 gears_configuration *bp;
726 int wire = MI_IS_WIREFRAME(mi);
730 bps = (gears_configuration *)
731 calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
733 fprintf(stderr, "%s: out of memory\n", progname);
737 bp = &bps[MI_SCREEN(mi)];
740 bp = &bps[MI_SCREEN(mi)];
742 bp->glx_context = init_GL(mi);
744 reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
748 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
749 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
750 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
751 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
753 glEnable(GL_LIGHTING);
755 glEnable(GL_DEPTH_TEST);
756 glEnable(GL_CULL_FACE);
758 glLightfv(GL_LIGHT0, GL_POSITION, pos);
759 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
760 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
761 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
765 double spin_speed = 0.5;
766 double wander_speed = 0.01;
767 double spin_accel = 0.25;
769 bp->rot = make_rotator (do_spin ? spin_speed : 0,
770 do_spin ? spin_speed : 0,
771 do_spin ? spin_speed : 0,
773 do_wander ? wander_speed : 0,
776 bp->trackball = gltrackball_init ();
781 planetary_gears (mi);
786 int total_gears = MI_COUNT (mi);
788 if (total_gears <= 0)
789 total_gears = 3 + abs (BELLRAND (8) - 4); /* 3 - 7, mostly 3. */
791 bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
794 for (i = 0; i < total_gears; i++)
795 g = place_new_gear (mi, g);
799 /* Center gears in scene. */
801 GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
803 for (i = 0; i < bp->ngears; i++)
805 gear *g = bp->gears[i];
806 if (g->x - g->r < minx) minx = g->x - g->r;
807 if (g->x + g->r > maxx) maxx = g->x + g->r;
808 if (g->y - g->r < miny) miny = g->y - g->r;
809 if (g->y + g->r > maxy) maxy = g->y + g->r;
817 /* Now render each gear into its display list.
819 for (i = 0; i < bp->ngears; i++)
821 gear *g = bp->gears[i];
822 g->dlist = glGenLists (1);
825 check_gl_error ("glGenLists");
829 glNewList (g->dlist, GL_COMPILE);
830 g->polygons += draw_involute_gear (g, wire);
839 draw_gears (ModeInfo *mi)
841 gears_configuration *bp = &bps[MI_SCREEN(mi)];
842 Display *dpy = MI_DISPLAY(mi);
843 Window window = MI_WINDOW(mi);
846 if (!bp->glx_context)
849 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
851 glShadeModel(GL_SMOOTH);
853 glEnable(GL_DEPTH_TEST);
854 glEnable(GL_NORMALIZE);
855 glEnable(GL_CULL_FACE);
857 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
863 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
864 glTranslatef ((x - 0.5) * 4,
868 gltrackball_rotate (bp->trackball);
870 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
872 /* add a little rotation for -no-spin mode */
876 glRotatef (x * 360, 1.0, 0.0, 0.0);
877 glRotatef (y * 360, 0.0, 1.0, 0.0);
878 glRotatef (z * 360, 0.0, 0.0, 1.0);
881 /* Center the scene's bounding box in the window,
885 GLfloat w = bp->bbox.x2 - bp->bbox.x1;
886 GLfloat h = bp->bbox.y2 - bp->bbox.y1;
887 GLfloat s = 10.0 / (w > h ? w : h);
889 glTranslatef (-(bp->bbox.x1 + w/2),
890 -(bp->bbox.y1 + h/2),
894 mi->polygon_count = 0;
896 for (i = 0; i < bp->ngears; i++)
898 gear *g = bp->gears[i];
902 glTranslatef (g->x, g->y, g->z);
903 glRotatef (g->th, 0, 0, 1);
905 glCallList (g->dlist);
906 mi->polygon_count += g->polygons;
913 glCallList (bp->armature_dlist);
914 mi->polygon_count += bp->armature_polygons;
920 if (!bp->button_down_p)
921 for (i = 0; i < bp->ngears; i++)
923 gear *g = bp->gears[i];
924 double off = g->ratio * 5 * speed;
931 if (mi->fps_p) do_fps (mi);
934 glXSwapBuffers(dpy, window);
937 XSCREENSAVER_MODULE ("Gears", gears)