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" \
19 "*suppressRotationAnimation: True\n" \
21 # define refresh_gears 0
22 # define release_gears 0
24 #define countof(x) (sizeof((x))/sizeof((*x)))
26 #include "xlockmore.h"
31 #include "gltrackball.h"
34 #ifdef USE_GL /* whole file */
37 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
39 #define DEF_SPIN "True"
40 #define DEF_WANDER "True"
41 #define DEF_SPEED "1.0"
44 GLXContext *glx_context;
46 trackball_state *trackball;
53 GLuint armature_dlist;
54 int armature_polygons;
56 struct { GLfloat x1, y1, x2, y2; } bbox;
58 } gears_configuration;
60 static gears_configuration *bps = NULL;
64 static Bool do_wander;
66 static XrmOptionDescRec opts[] = {
67 { "-spin", ".spin", XrmoptionNoArg, "True" },
68 { "+spin", ".spin", XrmoptionNoArg, "False" },
69 { "-speed", ".speed", XrmoptionSepArg, 0 },
70 { "-wander", ".wander", XrmoptionNoArg, "True" },
71 { "+wander", ".wander", XrmoptionNoArg, "False" },
74 static argtype vars[] = {
75 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
76 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
77 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
80 ENTRYPOINT ModeSpecOpt gears_opts = {countof(opts), opts, countof(vars), vars, NULL};
83 /* Window management, etc
86 reshape_gears (ModeInfo *mi, int width, int height)
88 GLfloat h = (GLfloat) height / (GLfloat) width;
90 glViewport (0, 0, (GLint) width, (GLint) height);
92 glMatrixMode(GL_PROJECTION);
94 gluPerspective (30.0, 1/h, 1.0, 100.0);
96 glMatrixMode(GL_MODELVIEW);
98 gluLookAt( 0.0, 0.0, 30.0,
102 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
104 int o = (int) current_device_rotation();
105 if (o != 0 && o != 180 && o != -180)
106 glScalef (1/h, 1/h, 1/h);
110 glClear(GL_COLOR_BUFFER_BIT);
118 glDeleteLists (g->dlist, 1);
123 /* Create and return a new gear sized for placement next to or on top of
124 the given parent gear (if any.) Returns 0 if out of memory.
125 [Mostly lifted from pinion.c]
128 new_gear (ModeInfo *mi, gear *parent)
130 gears_configuration *bp = &bps[MI_SCREEN(mi)];
131 gear *g = (gear *) calloc (1, sizeof (*g));
132 static unsigned long id = 0; /* only used in debugging output */
137 /* Pick the size of the teeth.
139 if (parent) /* adjascent gears need matching teeth */
141 g->tooth_w = parent->tooth_w;
142 g->tooth_h = parent->tooth_h;
143 g->tooth_slope = -parent->tooth_slope;
145 else /* gears that begin trains get any size they want */
147 g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
148 g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
150 g->tooth_slope = ((random() % 8)
152 : 0.5 + BELLRAND(1));
156 /* Pick the number of teeth, and thus, the radius.
161 if (!parent || bp->ngears > 4)
162 g->nteeth = 5 + BELLRAND (20);
164 g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
166 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
167 g->r = c / (M_PI * 2); /* c = 2 pi r */
170 g->thickness = g->tooth_w + frand (g->r);
171 g->thickness2 = g->thickness * 0.7;
172 g->thickness3 = g->thickness;
176 g->color[0] = 0.5 + frand(0.5);
177 g->color[1] = 0.5 + frand(0.5);
178 g->color[2] = 0.5 + frand(0.5);
181 g->color2[0] = g->color[0] * 0.85;
182 g->color2[1] = g->color[1] * 0.85;
183 g->color2[2] = g->color[2] * 0.85;
184 g->color2[3] = g->color[3];
187 /* Decide on shape of gear interior:
188 - just a ring with teeth;
189 - that, plus a thinner in-set "plate" in the middle;
190 - that, plus a thin raised "lip" on the inner plate;
191 - or, a wide lip (really, a thicker third inner plate.)
193 if ((random() % 10) == 0)
195 /* inner_r can go all the way in; there's no inset disc. */
196 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
202 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
203 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
204 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
207 if (g->inner_r2 > (g->r * 0.2))
209 int nn = (random() % 10);
211 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
212 else if (nn <= 7 && g->inner_r2 >= 0.1)
213 g->inner_r3 = g->inner_r2 - 0.01;
217 /* If we have three discs, sometimes make the middle disc be spokes.
219 if (g->inner_r3 && ((random() % 5) == 0))
221 g->spokes = 2 + BELLRAND (5);
222 g->spoke_thickness = 1 + frand(7.0);
223 if (g->spokes == 2 && g->spoke_thickness < 2)
224 g->spoke_thickness += 1;
227 /* Sometimes add little nubbly bits, if there is room.
232 involute_biggest_ring (g, 0, &size, 0);
233 if (size > g->r * 0.2 && (random() % 5) == 0)
235 g->nubs = 1 + (random() % 16);
236 if (g->nubs > 8) g->nubs = 1;
240 if (g->inner_r3 > g->inner_r2) abort();
241 if (g->inner_r2 > g->inner_r) abort();
242 if (g->inner_r > g->r) abort();
244 /* Decide how complex the polygon model should be.
247 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
248 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
249 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
250 else if (pix <= 25) g->size = INVOLUTE_LARGE;
251 else g->size = INVOLUTE_HUGE;
260 /* Given a newly-created gear, place it next to its parent in the scene,
261 with its teeth meshed and the proper velocity. Returns False if it
262 didn't work. (Call this a bunch of times until either it works, or
263 you decide it's probably not going to.)
264 [Mostly lifted from pinion.c]
267 place_gear (ModeInfo *mi, gear *g, gear *parent)
269 gears_configuration *bp = &bps[MI_SCREEN(mi)];
271 /* Compute this gear's velocity.
275 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
276 g->th = 1; /* not 0 */
280 /* Gearing ratio is the ratio of the number of teeth to previous gear
281 (which is also the ratio of the circumferences.)
283 g->ratio = (double) parent->nteeth / (double) g->nteeth;
285 /* Set our initial rotation to match that of the previous gear,
286 multiplied by the gearing ratio. (This is finessed later,
287 once we know the exact position of the gear relative to its
290 g->th = -(parent->th * g->ratio);
292 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
294 double off = (180.0 / g->nteeth);
301 /* ratios are cumulative for all gears in the train. */
302 g->ratio *= parent->ratio;
306 if (parent) /* Place the gear next to the parent. */
308 double r_off = parent->r + g->r;
311 angle = (random() % 360) - 180; /* -180 to +180 degrees */
313 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
314 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
317 /* avoid accidentally changing sign of "th" in the math below. */
318 g->th += (g->th > 0 ? 360 : -360);
320 /* Adjust the rotation of the gear so that its teeth line up with its
321 parent, based on the position of the gear and the current rotation
325 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
326 double g_c = 2 * M_PI * g->r; /* circumference of g */
328 double p_t = p_c * (angle/360.0); /* distance travelled along
329 circumference of parent when
330 moving "angle" degrees along
332 double g_rat = p_t / g_c; /* if travelling that distance
333 along circumference of g,
334 ratio of g's circumference
336 double g_th = 360.0 * g_rat; /* that ratio in degrees */
338 g->th += angle + g_th;
342 /* If the position we picked for this gear causes it to overlap
343 with any earlier gear in the train, give up.
348 for (i = bp->ngears-1; i >= 0; i--)
350 gear *og = bp->gears[i];
352 if (og == g) continue;
353 if (og == parent) continue;
354 if (g->z != og->z) continue; /* Ignore unless on same layer */
356 /* Collision detection without sqrt:
357 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
358 d < r1 + r2 d^2 < (r1 + r2)^2
360 if (((g->x - og->x) * (g->x - og->x) +
361 (g->y - og->y) * (g->y - og->y)) <
362 ((g->r + g->tooth_h + og->r + og->tooth_h) *
363 (g->r + g->tooth_h + og->r + og->tooth_h)))
372 /* Make a new gear, place it next to its parent in the scene,
373 with its teeth meshed and the proper velocity. Returns the gear;
374 or 0 if it didn't work. (Call this a bunch of times until either
375 it works, or you decide it's probably not going to.)
376 [Mostly lifted from pinion.c]
379 place_new_gear (ModeInfo *mi, gear *parent)
381 gears_configuration *bp = &bps[MI_SCREEN(mi)];
388 if (loop_count >= 100)
396 g = new_gear (mi, parent);
397 if (!g) return 0; /* out of memory? */
399 if (place_gear (mi, g, parent))
405 /* We got a gear, and it is properly positioned.
406 Insert it in the scene.
408 bp->gears[bp->ngears++] = g;
415 GLfloat width1, GLfloat height1,
416 GLfloat width2, GLfloat height2,
420 glShadeModel(GL_FLAT);
422 #if 0 /* don't need these - they're embedded in other objects */
425 glNormal3f(-1, 0, 0);
426 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
427 glVertex3f(-length/2, -width1/2, -height1/2);
428 glVertex3f(-length/2, width1/2, -height1/2);
429 glVertex3f(-length/2, width1/2, height1/2);
430 glVertex3f(-length/2, -width1/2, height1/2);
437 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
438 glVertex3f(length/2, -width2/2, -height2/2);
439 glVertex3f(length/2, width2/2, -height2/2);
440 glVertex3f(length/2, width2/2, height2/2);
441 glVertex3f(length/2, -width2/2, height2/2);
448 glNormal3f(0, 0, -1);
449 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
450 glVertex3f(-length/2, -width1/2, -height1/2);
451 glVertex3f(-length/2, width1/2, -height1/2);
452 glVertex3f( length/2, width2/2, -height2/2);
453 glVertex3f( length/2, -width2/2, -height2/2);
460 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
461 glVertex3f(-length/2, -width1/2, height1/2);
462 glVertex3f(-length/2, width1/2, height1/2);
463 glVertex3f( length/2, width2/2, height2/2);
464 glVertex3f( length/2, -width2/2, height2/2);
470 glNormal3f(0, -1, 0);
471 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
472 glVertex3f(-length/2, -width1/2, -height1/2);
473 glVertex3f(-length/2, -width1/2, height1/2);
474 glVertex3f( length/2, -width2/2, height2/2);
475 glVertex3f( length/2, -width2/2, -height2/2);
482 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
483 glVertex3f(-length/2, width1/2, -height1/2);
484 glVertex3f(-length/2, width1/2, height1/2);
485 glVertex3f( length/2, width2/2, height2/2);
486 glVertex3f( length/2, width2/2, -height2/2);
497 ctube (GLfloat diameter, GLfloat width, Bool wire)
502 32, True, True, wire);
507 armature (ModeInfo *mi)
509 gears_configuration *bp = &bps[MI_SCREEN(mi)];
510 int wire = MI_IS_WIREFRAME(mi);
512 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
513 GLfloat shiny = 128.0;
516 color[0] = 0.5 + frand(0.5);
517 color[1] = 0.5 + frand(0.5);
518 color[2] = 0.5 + frand(0.5);
521 bp->armature_polygons = 0;
523 bp->armature_dlist = glGenLists (1);
524 if (! bp->armature_dlist)
526 check_gl_error ("glGenLists");
530 glNewList (bp->armature_dlist, GL_COMPILE);
532 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
533 glMateriali (GL_FRONT, GL_SHININESS, shiny);
534 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
535 glColor3f (color[0], color[1], color[2]);
540 GLfloat s = bp->gears[0]->r * 2.7;
545 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
546 glRotatef (30, 0, 0, 1);
548 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
551 glTranslatef(0.0, 4.2, -1);
552 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
553 glTranslatef(0, 0, 1.8);
554 bp->armature_polygons += ctube (0.7, 0.7, wire);
558 glRotatef(120, 0.0, 0.0, 1.0);
559 glTranslatef(0.0, 4.2, -1);
560 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
561 glTranslatef(0, 0, 1.8);
562 bp->armature_polygons += ctube (0.7, 0.7, wire);
566 glRotatef(240, 0.0, 0.0, 1.0);
567 glTranslatef(0.0, 4.2, -1);
568 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
569 glTranslatef(0, 0, 1.8);
570 bp->armature_polygons += ctube (0.7, 0.7, wire);
573 glTranslatef(0, 0, 1.5); /* center disk */
574 bp->armature_polygons += ctube (1.5, 2, wire);
577 glRotatef(270, 0, 0, 1);
578 glRotatef(-10, 0, 1, 0);
579 glTranslatef(-2.2, 0, 0);
580 bp->armature_polygons += arm (4.0, 1.0, 0.5,
581 2.0, 1.0, wire); /* arm 1 */
585 glRotatef(30, 0, 0, 1);
586 glRotatef(-10, 0, 1, 0);
587 glTranslatef(-2.2, 0, 0);
588 bp->armature_polygons += arm (4.0, 1.0, 0.5,
589 2.0, 1.0, wire); /* arm 2 */
593 glRotatef(150, 0, 0, 1);
594 glRotatef(-10, 0, 1, 0);
595 glTranslatef(-2.2, 0, 0);
596 bp->armature_polygons += arm (4.0, 1.0, 0.5,
597 2.0, 1.0, wire); /* arm 3 */
607 planetary_gears (ModeInfo *mi)
609 gears_configuration *bp = &bps[MI_SCREEN(mi)];
610 gear *g0, *g1, *g2, *g3, *g4;
611 GLfloat distance = 2.02;
613 bp->planetary_p = True;
615 g0 = new_gear (mi, 0);
616 g1 = new_gear (mi, 0);
617 g2 = new_gear (mi, 0);
618 g3 = new_gear (mi, 0);
619 g4 = new_gear (mi, 0);
621 if (! place_gear (mi, g0, 0)) abort();
622 if (! place_gear (mi, g1, 0)) abort();
623 if (! place_gear (mi, g2, 0)) abort();
624 if (! place_gear (mi, g3, 0)) abort();
625 if (! place_gear (mi, g4, 0)) abort();
627 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
628 g0->tooth_w = g0->r / g0->nteeth;
629 g0->tooth_h = g0->tooth_w * 2.8;
631 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
648 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
649 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
651 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
652 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
654 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
655 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
661 /* rotate central gear 1/2 tooth-size if odd number of teeth */
663 g4->th -= (180.0 / g4->nteeth);
665 g0->inverted_p = True;
668 g0->nteeth = g1->nteeth * 3;
669 g0->r = g1->r * 3.05;
670 g0->inner_r = g0->r * 0.8;
673 g0->th = g1->th + (180 / g0->nteeth);
674 g0->ratio = g1->ratio / 3;
679 g0->size = INVOLUTE_LARGE;
681 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
684 bp->gears[bp->ngears++] = g1;
685 bp->gears[bp->ngears++] = g2;
686 bp->gears[bp->ngears++] = g3;
687 bp->gears[bp->ngears++] = g4;
688 bp->gears[bp->ngears++] = g0;
695 init_gears (ModeInfo *mi)
697 gears_configuration *bp;
698 int wire = MI_IS_WIREFRAME(mi);
702 bps = (gears_configuration *)
703 calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
705 fprintf(stderr, "%s: out of memory\n", progname);
710 bp = &bps[MI_SCREEN(mi)];
712 bp->glx_context = init_GL(mi);
714 reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
718 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
719 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
720 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
721 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
723 glEnable(GL_LIGHTING);
725 glEnable(GL_DEPTH_TEST);
726 glEnable(GL_CULL_FACE);
728 glLightfv(GL_LIGHT0, GL_POSITION, pos);
729 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
730 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
731 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
736 double spin_speed = 0.5;
737 double wander_speed = 0.01;
738 double spin_accel = 0.25;
740 bp->rot = make_rotator (do_spin ? spin_speed : 0,
741 do_spin ? spin_speed : 0,
742 do_spin ? spin_speed : 0,
744 do_wander ? wander_speed : 0,
747 bp->trackball = gltrackball_init (True);
752 for (i = 0; i < bp->ngears; i++)
753 free_gear (bp->gears[i]);
761 planetary_gears (mi);
766 int total_gears = MI_COUNT (mi);
768 bp->planetary_p = False;
770 if (total_gears <= 0)
771 total_gears = 3 + fabs (BELLRAND (8) - 4); /* 3 - 7, mostly 3. */
772 bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
775 for (i = 0; i < total_gears; i++)
776 g = place_new_gear (mi, g);
780 /* Center gears in scene. */
782 GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
784 for (i = 0; i < bp->ngears; i++)
786 gear *g = bp->gears[i];
787 if (g->x - g->r < minx) minx = g->x - g->r;
788 if (g->x + g->r > maxx) maxx = g->x + g->r;
789 if (g->y - g->r < miny) miny = g->y - g->r;
790 if (g->y + g->r > maxy) maxy = g->y + g->r;
798 /* Now render each gear into its display list.
800 for (i = 0; i < bp->ngears; i++)
802 gear *g = bp->gears[i];
803 g->dlist = glGenLists (1);
806 check_gl_error ("glGenLists");
810 glNewList (g->dlist, GL_COMPILE);
811 g->polygons += draw_involute_gear (g, wire);
820 draw_gears (ModeInfo *mi)
822 gears_configuration *bp = &bps[MI_SCREEN(mi)];
823 Display *dpy = MI_DISPLAY(mi);
824 Window window = MI_WINDOW(mi);
827 if (!bp->glx_context)
830 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
832 glShadeModel(GL_SMOOTH);
834 glEnable(GL_DEPTH_TEST);
835 glEnable(GL_NORMALIZE);
836 glEnable(GL_CULL_FACE);
838 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
844 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
845 glTranslatef ((x - 0.5) * 4,
849 gltrackball_rotate (bp->trackball);
851 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
853 /* add a little rotation for -no-spin mode */
857 glRotatef (x * 360, 1.0, 0.0, 0.0);
858 glRotatef (y * 360, 0.0, 1.0, 0.0);
859 glRotatef (z * 360, 0.0, 0.0, 1.0);
862 /* Center the scene's bounding box in the window,
866 GLfloat w = bp->bbox.x2 - bp->bbox.x1;
867 GLfloat h = bp->bbox.y2 - bp->bbox.y1;
868 GLfloat s = 10.0 / (w > h ? w : h);
870 glTranslatef (-(bp->bbox.x1 + w/2),
871 -(bp->bbox.y1 + h/2),
875 mi->polygon_count = 0;
877 for (i = 0; i < bp->ngears; i++)
879 gear *g = bp->gears[i];
883 glTranslatef (g->x, g->y, g->z);
884 glRotatef (g->th, 0, 0, 1);
886 glCallList (g->dlist);
887 mi->polygon_count += g->polygons;
894 glCallList (bp->armature_dlist);
895 mi->polygon_count += bp->armature_polygons;
901 if (!bp->button_down_p)
902 for (i = 0; i < bp->ngears; i++)
904 gear *g = bp->gears[i];
905 double off = g->ratio * 5 * speed;
912 if (mi->fps_p) do_fps (mi);
915 glXSwapBuffers(dpy, window);
919 gears_handle_event (ModeInfo *mi, XEvent *event)
921 gears_configuration *bp = &bps[MI_SCREEN(mi)];
923 if (gltrackball_event_handler (event, bp->trackball,
924 MI_WIDTH (mi), MI_HEIGHT (mi),
927 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
936 XSCREENSAVER_MODULE ("Gears", gears)