1 /* gears, Copyright (c) 2007-2014 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);
109 glDeleteLists (g->dlist, 1);
114 /* Create and return a new gear sized for placement next to or on top of
115 the given parent gear (if any.) Returns 0 if out of memory.
116 [Mostly lifted from pinion.c]
119 new_gear (ModeInfo *mi, gear *parent)
121 gears_configuration *bp = &bps[MI_SCREEN(mi)];
122 gear *g = (gear *) calloc (1, sizeof (*g));
123 static unsigned long id = 0; /* only used in debugging output */
128 /* Pick the size of the teeth.
130 if (parent) /* adjascent gears need matching teeth */
132 g->tooth_w = parent->tooth_w;
133 g->tooth_h = parent->tooth_h;
134 g->tooth_slope = -parent->tooth_slope;
136 else /* gears that begin trains get any size they want */
138 g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
139 g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
141 g->tooth_slope = ((random() % 8)
143 : 0.5 + BELLRAND(1));
147 /* Pick the number of teeth, and thus, the radius.
152 if (!parent || bp->ngears > 4)
153 g->nteeth = 5 + BELLRAND (20);
155 g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
157 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
158 g->r = c / (M_PI * 2); /* c = 2 pi r */
161 g->thickness = g->tooth_w + frand (g->r);
162 g->thickness2 = g->thickness * 0.7;
163 g->thickness3 = g->thickness;
167 g->color[0] = 0.5 + frand(0.5);
168 g->color[1] = 0.5 + frand(0.5);
169 g->color[2] = 0.5 + frand(0.5);
172 g->color2[0] = g->color[0] * 0.85;
173 g->color2[1] = g->color[1] * 0.85;
174 g->color2[2] = g->color[2] * 0.85;
175 g->color2[3] = g->color[3];
178 /* Decide on shape of gear interior:
179 - just a ring with teeth;
180 - that, plus a thinner in-set "plate" in the middle;
181 - that, plus a thin raised "lip" on the inner plate;
182 - or, a wide lip (really, a thicker third inner plate.)
184 if ((random() % 10) == 0)
186 /* inner_r can go all the way in; there's no inset disc. */
187 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
193 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
194 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
195 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
198 if (g->inner_r2 > (g->r * 0.2))
200 int nn = (random() % 10);
202 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
203 else if (nn <= 7 && g->inner_r2 >= 0.1)
204 g->inner_r3 = g->inner_r2 - 0.01;
208 /* If we have three discs, sometimes make the middle disc be spokes.
210 if (g->inner_r3 && ((random() % 5) == 0))
212 g->spokes = 2 + BELLRAND (5);
213 g->spoke_thickness = 1 + frand(7.0);
214 if (g->spokes == 2 && g->spoke_thickness < 2)
215 g->spoke_thickness += 1;
218 /* Sometimes add little nubbly bits, if there is room.
223 involute_biggest_ring (g, 0, &size, 0);
224 if (size > g->r * 0.2 && (random() % 5) == 0)
226 g->nubs = 1 + (random() % 16);
227 if (g->nubs > 8) g->nubs = 1;
231 if (g->inner_r3 > g->inner_r2) abort();
232 if (g->inner_r2 > g->inner_r) abort();
233 if (g->inner_r > g->r) abort();
235 /* Decide how complex the polygon model should be.
238 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
239 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
240 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
241 else if (pix <= 25) g->size = INVOLUTE_LARGE;
242 else g->size = INVOLUTE_HUGE;
251 /* Given a newly-created gear, place it next to its parent in the scene,
252 with its teeth meshed and the proper velocity. Returns False if it
253 didn't work. (Call this a bunch of times until either it works, or
254 you decide it's probably not going to.)
255 [Mostly lifted from pinion.c]
258 place_gear (ModeInfo *mi, gear *g, gear *parent)
260 gears_configuration *bp = &bps[MI_SCREEN(mi)];
262 /* Compute this gear's velocity.
266 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
267 g->th = 1; /* not 0 */
271 /* Gearing ratio is the ratio of the number of teeth to previous gear
272 (which is also the ratio of the circumferences.)
274 g->ratio = (double) parent->nteeth / (double) g->nteeth;
276 /* Set our initial rotation to match that of the previous gear,
277 multiplied by the gearing ratio. (This is finessed later,
278 once we know the exact position of the gear relative to its
281 g->th = -(parent->th * g->ratio);
283 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
285 double off = (180.0 / g->nteeth);
292 /* ratios are cumulative for all gears in the train. */
293 g->ratio *= parent->ratio;
297 if (parent) /* Place the gear next to the parent. */
299 double r_off = parent->r + g->r;
302 angle = (random() % 360) - 180; /* -180 to +180 degrees */
304 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
305 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
308 /* avoid accidentally changing sign of "th" in the math below. */
309 g->th += (g->th > 0 ? 360 : -360);
311 /* Adjust the rotation of the gear so that its teeth line up with its
312 parent, based on the position of the gear and the current rotation
316 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
317 double g_c = 2 * M_PI * g->r; /* circumference of g */
319 double p_t = p_c * (angle/360.0); /* distance travelled along
320 circumference of parent when
321 moving "angle" degrees along
323 double g_rat = p_t / g_c; /* if travelling that distance
324 along circumference of g,
325 ratio of g's circumference
327 double g_th = 360.0 * g_rat; /* that ratio in degrees */
329 g->th += angle + g_th;
333 /* If the position we picked for this gear causes it to overlap
334 with any earlier gear in the train, give up.
339 for (i = bp->ngears-1; i >= 0; i--)
341 gear *og = bp->gears[i];
343 if (og == g) continue;
344 if (og == parent) continue;
345 if (g->z != og->z) continue; /* Ignore unless on same layer */
347 /* Collision detection without sqrt:
348 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
349 d < r1 + r2 d^2 < (r1 + r2)^2
351 if (((g->x - og->x) * (g->x - og->x) +
352 (g->y - og->y) * (g->y - og->y)) <
353 ((g->r + g->tooth_h + og->r + og->tooth_h) *
354 (g->r + g->tooth_h + og->r + og->tooth_h)))
363 /* Make a new gear, place it next to its parent in the scene,
364 with its teeth meshed and the proper velocity. Returns the gear;
365 or 0 if it didn't work. (Call this a bunch of times until either
366 it works, or you decide it's probably not going to.)
367 [Mostly lifted from pinion.c]
370 place_new_gear (ModeInfo *mi, gear *parent)
372 gears_configuration *bp = &bps[MI_SCREEN(mi)];
379 if (loop_count >= 100)
387 g = new_gear (mi, parent);
388 if (!g) return 0; /* out of memory? */
390 if (place_gear (mi, g, parent))
396 /* We got a gear, and it is properly positioned.
397 Insert it in the scene.
399 bp->gears[bp->ngears++] = g;
406 GLfloat width1, GLfloat height1,
407 GLfloat width2, GLfloat height2,
411 glShadeModel(GL_FLAT);
413 #if 0 /* don't need these - they're embedded in other objects */
416 glNormal3f(-1, 0, 0);
417 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
418 glVertex3f(-length/2, -width1/2, -height1/2);
419 glVertex3f(-length/2, width1/2, -height1/2);
420 glVertex3f(-length/2, width1/2, height1/2);
421 glVertex3f(-length/2, -width1/2, height1/2);
428 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
429 glVertex3f(length/2, -width2/2, -height2/2);
430 glVertex3f(length/2, width2/2, -height2/2);
431 glVertex3f(length/2, width2/2, height2/2);
432 glVertex3f(length/2, -width2/2, height2/2);
439 glNormal3f(0, 0, -1);
440 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
441 glVertex3f(-length/2, -width1/2, -height1/2);
442 glVertex3f(-length/2, width1/2, -height1/2);
443 glVertex3f( length/2, width2/2, -height2/2);
444 glVertex3f( length/2, -width2/2, -height2/2);
451 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
452 glVertex3f(-length/2, -width1/2, height1/2);
453 glVertex3f(-length/2, width1/2, height1/2);
454 glVertex3f( length/2, width2/2, height2/2);
455 glVertex3f( length/2, -width2/2, height2/2);
461 glNormal3f(0, -1, 0);
462 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
463 glVertex3f(-length/2, -width1/2, -height1/2);
464 glVertex3f(-length/2, -width1/2, height1/2);
465 glVertex3f( length/2, -width2/2, height2/2);
466 glVertex3f( length/2, -width2/2, -height2/2);
473 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
474 glVertex3f(-length/2, width1/2, -height1/2);
475 glVertex3f(-length/2, width1/2, height1/2);
476 glVertex3f( length/2, width2/2, height2/2);
477 glVertex3f( length/2, width2/2, -height2/2);
488 ctube (GLfloat diameter, GLfloat width, Bool wire)
493 32, True, True, wire);
498 armature (ModeInfo *mi)
500 gears_configuration *bp = &bps[MI_SCREEN(mi)];
501 int wire = MI_IS_WIREFRAME(mi);
503 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
504 GLfloat shiny = 128.0;
507 color[0] = 0.5 + frand(0.5);
508 color[1] = 0.5 + frand(0.5);
509 color[2] = 0.5 + frand(0.5);
512 bp->armature_polygons = 0;
514 bp->armature_dlist = glGenLists (1);
515 if (! bp->armature_dlist)
517 check_gl_error ("glGenLists");
521 glNewList (bp->armature_dlist, GL_COMPILE);
523 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
524 glMateriali (GL_FRONT, GL_SHININESS, shiny);
525 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
526 glColor3f (color[0], color[1], color[2]);
531 GLfloat s = bp->gears[0]->r * 2.7;
536 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
537 glRotatef (30, 0, 0, 1);
539 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
542 glTranslatef(0.0, 4.2, -1);
543 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
544 glTranslatef(0, 0, 1.8);
545 bp->armature_polygons += ctube (0.7, 0.7, wire);
549 glRotatef(120, 0.0, 0.0, 1.0);
550 glTranslatef(0.0, 4.2, -1);
551 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
552 glTranslatef(0, 0, 1.8);
553 bp->armature_polygons += ctube (0.7, 0.7, wire);
557 glRotatef(240, 0.0, 0.0, 1.0);
558 glTranslatef(0.0, 4.2, -1);
559 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
560 glTranslatef(0, 0, 1.8);
561 bp->armature_polygons += ctube (0.7, 0.7, wire);
564 glTranslatef(0, 0, 1.5); /* center disk */
565 bp->armature_polygons += ctube (1.5, 2, wire);
568 glRotatef(270, 0, 0, 1);
569 glRotatef(-10, 0, 1, 0);
570 glTranslatef(-2.2, 0, 0);
571 bp->armature_polygons += arm (4.0, 1.0, 0.5,
572 2.0, 1.0, wire); /* arm 1 */
576 glRotatef(30, 0, 0, 1);
577 glRotatef(-10, 0, 1, 0);
578 glTranslatef(-2.2, 0, 0);
579 bp->armature_polygons += arm (4.0, 1.0, 0.5,
580 2.0, 1.0, wire); /* arm 2 */
584 glRotatef(150, 0, 0, 1);
585 glRotatef(-10, 0, 1, 0);
586 glTranslatef(-2.2, 0, 0);
587 bp->armature_polygons += arm (4.0, 1.0, 0.5,
588 2.0, 1.0, wire); /* arm 3 */
598 planetary_gears (ModeInfo *mi)
600 gears_configuration *bp = &bps[MI_SCREEN(mi)];
601 gear *g0, *g1, *g2, *g3, *g4;
602 GLfloat distance = 2.02;
604 bp->planetary_p = True;
606 g0 = new_gear (mi, 0);
607 g1 = new_gear (mi, 0);
608 g2 = new_gear (mi, 0);
609 g3 = new_gear (mi, 0);
610 g4 = new_gear (mi, 0);
612 if (! place_gear (mi, g0, 0)) abort();
613 if (! place_gear (mi, g1, 0)) abort();
614 if (! place_gear (mi, g2, 0)) abort();
615 if (! place_gear (mi, g3, 0)) abort();
616 if (! place_gear (mi, g4, 0)) abort();
618 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
619 g0->tooth_w = g0->r / g0->nteeth;
620 g0->tooth_h = g0->tooth_w * 2.8;
622 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
639 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
640 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
642 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
643 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
645 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
646 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
652 /* rotate central gear 1/2 tooth-size if odd number of teeth */
654 g4->th -= (180.0 / g4->nteeth);
656 g0->inverted_p = True;
659 g0->nteeth = g1->nteeth * 3;
660 g0->r = g1->r * 3.05;
661 g0->inner_r = g0->r * 0.8;
664 g0->th = g1->th + (180 / g0->nteeth);
665 g0->ratio = g1->ratio / 3;
670 g0->size = INVOLUTE_LARGE;
672 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
675 bp->gears[bp->ngears++] = g1;
676 bp->gears[bp->ngears++] = g2;
677 bp->gears[bp->ngears++] = g3;
678 bp->gears[bp->ngears++] = g4;
679 bp->gears[bp->ngears++] = g0;
686 init_gears (ModeInfo *mi)
688 gears_configuration *bp;
689 int wire = MI_IS_WIREFRAME(mi);
693 bps = (gears_configuration *)
694 calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
696 fprintf(stderr, "%s: out of memory\n", progname);
701 bp = &bps[MI_SCREEN(mi)];
703 bp->glx_context = init_GL(mi);
705 reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
709 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
710 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
711 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
712 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
714 glEnable(GL_LIGHTING);
716 glEnable(GL_DEPTH_TEST);
717 glEnable(GL_CULL_FACE);
719 glLightfv(GL_LIGHT0, GL_POSITION, pos);
720 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
721 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
722 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
727 double spin_speed = 0.5;
728 double wander_speed = 0.01;
729 double spin_accel = 0.25;
731 bp->rot = make_rotator (do_spin ? spin_speed : 0,
732 do_spin ? spin_speed : 0,
733 do_spin ? spin_speed : 0,
735 do_wander ? wander_speed : 0,
738 bp->trackball = gltrackball_init (True);
743 for (i = 0; i < bp->ngears; i++)
744 free_gear (bp->gears[i]);
752 planetary_gears (mi);
757 int total_gears = MI_COUNT (mi);
759 bp->planetary_p = False;
761 if (total_gears <= 0)
762 total_gears = 3 + abs (BELLRAND (8) - 4); /* 3 - 7, mostly 3. */
763 bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
766 for (i = 0; i < total_gears; i++)
767 g = place_new_gear (mi, g);
771 /* Center gears in scene. */
773 GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
775 for (i = 0; i < bp->ngears; i++)
777 gear *g = bp->gears[i];
778 if (g->x - g->r < minx) minx = g->x - g->r;
779 if (g->x + g->r > maxx) maxx = g->x + g->r;
780 if (g->y - g->r < miny) miny = g->y - g->r;
781 if (g->y + g->r > maxy) maxy = g->y + g->r;
789 /* Now render each gear into its display list.
791 for (i = 0; i < bp->ngears; i++)
793 gear *g = bp->gears[i];
794 g->dlist = glGenLists (1);
797 check_gl_error ("glGenLists");
801 glNewList (g->dlist, GL_COMPILE);
802 g->polygons += draw_involute_gear (g, wire);
811 draw_gears (ModeInfo *mi)
813 gears_configuration *bp = &bps[MI_SCREEN(mi)];
814 Display *dpy = MI_DISPLAY(mi);
815 Window window = MI_WINDOW(mi);
818 if (!bp->glx_context)
821 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
823 glShadeModel(GL_SMOOTH);
825 glEnable(GL_DEPTH_TEST);
826 glEnable(GL_NORMALIZE);
827 glEnable(GL_CULL_FACE);
829 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
835 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
836 glTranslatef ((x - 0.5) * 4,
840 gltrackball_rotate (bp->trackball);
842 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
844 /* add a little rotation for -no-spin mode */
848 glRotatef (x * 360, 1.0, 0.0, 0.0);
849 glRotatef (y * 360, 0.0, 1.0, 0.0);
850 glRotatef (z * 360, 0.0, 0.0, 1.0);
853 /* Center the scene's bounding box in the window,
857 GLfloat w = bp->bbox.x2 - bp->bbox.x1;
858 GLfloat h = bp->bbox.y2 - bp->bbox.y1;
859 GLfloat s = 10.0 / (w > h ? w : h);
861 glTranslatef (-(bp->bbox.x1 + w/2),
862 -(bp->bbox.y1 + h/2),
866 mi->polygon_count = 0;
868 for (i = 0; i < bp->ngears; i++)
870 gear *g = bp->gears[i];
874 glTranslatef (g->x, g->y, g->z);
875 glRotatef (g->th, 0, 0, 1);
877 glCallList (g->dlist);
878 mi->polygon_count += g->polygons;
885 glCallList (bp->armature_dlist);
886 mi->polygon_count += bp->armature_polygons;
892 if (!bp->button_down_p)
893 for (i = 0; i < bp->ngears; i++)
895 gear *g = bp->gears[i];
896 double off = g->ratio * 5 * speed;
903 if (mi->fps_p) do_fps (mi);
906 glXSwapBuffers(dpy, window);
910 gears_handle_event (ModeInfo *mi, XEvent *event)
912 gears_configuration *bp = &bps[MI_SCREEN(mi)];
914 if (gltrackball_event_handler (event, bp->trackball,
915 MI_WIDTH (mi), MI_HEIGHT (mi),
918 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
927 XSCREENSAVER_MODULE ("Gears", gears)