1 /* gears, Copyright (c) 2007-2008 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 ||
128 event->xbutton.button == Button6 ||
129 event->xbutton.button == Button7))
131 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 10,
132 !!event->xbutton.state);
135 else if (event->xany.type == MotionNotify &&
138 gltrackball_track (bp->trackball,
139 event->xmotion.x, event->xmotion.y,
140 MI_WIDTH (mi), MI_HEIGHT (mi));
153 glDeleteLists (g->dlist, 1);
158 /* Create and return a new gear sized for placement next to or on top of
159 the given parent gear (if any.) Returns 0 if out of memory.
160 [Mostly lifted from pinion.c]
163 new_gear (ModeInfo *mi, gear *parent)
165 gears_configuration *bp = &bps[MI_SCREEN(mi)];
166 gear *g = (gear *) calloc (1, sizeof (*g));
167 static unsigned long id = 0; /* only used in debugging output */
172 /* Pick the size of the teeth.
174 if (parent) /* adjascent gears need matching teeth */
176 g->tooth_w = parent->tooth_w;
177 g->tooth_h = parent->tooth_h;
178 g->tooth_slope = -parent->tooth_slope;
180 else /* gears that begin trains get any size they want */
182 g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
183 g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
185 g->tooth_slope = ((random() % 8)
187 : 0.5 + BELLRAND(1));
191 /* Pick the number of teeth, and thus, the radius.
196 if (!parent || bp->ngears > 4)
197 g->nteeth = 5 + BELLRAND (20);
199 g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
201 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
202 g->r = c / (M_PI * 2); /* c = 2 pi r */
205 g->thickness = g->tooth_w + frand (g->r);
206 g->thickness2 = g->thickness * 0.7;
207 g->thickness3 = g->thickness;
211 g->color[0] = 0.5 + frand(0.5);
212 g->color[1] = 0.5 + frand(0.5);
213 g->color[2] = 0.5 + frand(0.5);
216 g->color2[0] = g->color[0] * 0.85;
217 g->color2[1] = g->color[1] * 0.85;
218 g->color2[2] = g->color[2] * 0.85;
219 g->color2[3] = g->color[3];
222 /* Decide on shape of gear interior:
223 - just a ring with teeth;
224 - that, plus a thinner in-set "plate" in the middle;
225 - that, plus a thin raised "lip" on the inner plate;
226 - or, a wide lip (really, a thicker third inner plate.)
228 if ((random() % 10) == 0)
230 /* inner_r can go all the way in; there's no inset disc. */
231 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
237 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
238 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
239 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
242 if (g->inner_r2 > (g->r * 0.2))
244 int nn = (random() % 10);
246 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
247 else if (nn <= 7 && g->inner_r2 >= 0.1)
248 g->inner_r3 = g->inner_r2 - 0.01;
252 /* If we have three discs, sometimes make the middle disc be spokes.
254 if (g->inner_r3 && ((random() % 5) == 0))
256 g->spokes = 2 + BELLRAND (5);
257 g->spoke_thickness = 1 + frand(7.0);
258 if (g->spokes == 2 && g->spoke_thickness < 2)
259 g->spoke_thickness += 1;
262 /* Sometimes add little nubbly bits, if there is room.
267 involute_biggest_ring (g, 0, &size, 0);
268 if (size > g->r * 0.2 && (random() % 5) == 0)
270 g->nubs = 1 + (random() % 16);
271 if (g->nubs > 8) g->nubs = 1;
275 if (g->inner_r3 > g->inner_r2) abort();
276 if (g->inner_r2 > g->inner_r) abort();
277 if (g->inner_r > g->r) abort();
279 /* Decide how complex the polygon model should be.
282 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
283 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
284 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
285 else g->size = INVOLUTE_LARGE;
294 /* Given a newly-created gear, place it next to its parent in the scene,
295 with its teeth meshed and the proper velocity. Returns False if it
296 didn't work. (Call this a bunch of times until either it works, or
297 you decide it's probably not going to.)
298 [Mostly lifted from pinion.c]
301 place_gear (ModeInfo *mi, gear *g, gear *parent)
303 gears_configuration *bp = &bps[MI_SCREEN(mi)];
305 /* Compute this gear's velocity.
309 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
310 g->th = 1; /* not 0 */
314 /* Gearing ratio is the ratio of the number of teeth to previous gear
315 (which is also the ratio of the circumferences.)
317 g->ratio = (double) parent->nteeth / (double) g->nteeth;
319 /* Set our initial rotation to match that of the previous gear,
320 multiplied by the gearing ratio. (This is finessed later,
321 once we know the exact position of the gear relative to its
324 g->th = -(parent->th * g->ratio);
326 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
328 double off = (180.0 / g->nteeth);
335 /* ratios are cumulative for all gears in the train. */
336 g->ratio *= parent->ratio;
340 if (parent) /* Place the gear next to the parent. */
342 double r_off = parent->r + g->r;
345 angle = (random() % 360) - 180; /* -180 to +180 degrees */
347 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
348 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
351 /* avoid accidentally changing sign of "th" in the math below. */
352 g->th += (g->th > 0 ? 360 : -360);
354 /* Adjust the rotation of the gear so that its teeth line up with its
355 parent, based on the position of the gear and the current rotation
359 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
360 double g_c = 2 * M_PI * g->r; /* circumference of g */
362 double p_t = p_c * (angle/360.0); /* distance travelled along
363 circumference of parent when
364 moving "angle" degrees along
366 double g_rat = p_t / g_c; /* if travelling that distance
367 along circumference of g,
368 ratio of g's circumference
370 double g_th = 360.0 * g_rat; /* that ratio in degrees */
372 g->th += angle + g_th;
376 /* If the position we picked for this gear causes it to overlap
377 with any earlier gear in the train, give up.
382 for (i = bp->ngears-1; i >= 0; i--)
384 gear *og = bp->gears[i];
386 if (og == g) continue;
387 if (og == parent) continue;
388 if (g->z != og->z) continue; /* Ignore unless on same layer */
390 /* Collision detection without sqrt:
391 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
392 d < r1 + r2 d^2 < (r1 + r2)^2
394 if (((g->x - og->x) * (g->x - og->x) +
395 (g->y - og->y) * (g->y - og->y)) <
396 ((g->r + g->tooth_h + og->r + og->tooth_h) *
397 (g->r + g->tooth_h + og->r + og->tooth_h)))
406 /* Make a new gear, place it next to its parent in the scene,
407 with its teeth meshed and the proper velocity. Returns the gear;
408 or 0 if it didn't work. (Call this a bunch of times until either
409 it works, or you decide it's probably not going to.)
410 [Mostly lifted from pinion.c]
413 place_new_gear (ModeInfo *mi, gear *parent)
415 gears_configuration *bp = &bps[MI_SCREEN(mi)];
422 if (loop_count >= 100)
430 g = new_gear (mi, parent);
431 if (!g) return 0; /* out of memory? */
433 if (place_gear (mi, g, parent))
439 /* We got a gear, and it is properly positioned.
440 Insert it in the scene.
442 bp->gears[bp->ngears++] = g;
449 GLfloat width1, GLfloat height1,
450 GLfloat width2, GLfloat height2,
454 glShadeModel(GL_FLAT);
456 #if 0 /* don't need these - they're embedded in other objects */
459 glNormal3f(-1, 0, 0);
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, width1/2, height1/2);
464 glVertex3f(-length/2, -width1/2, height1/2);
471 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
472 glVertex3f(length/2, -width2/2, -height2/2);
473 glVertex3f(length/2, width2/2, -height2/2);
474 glVertex3f(length/2, width2/2, height2/2);
475 glVertex3f(length/2, -width2/2, height2/2);
482 glNormal3f(0, 0, -1);
483 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
484 glVertex3f(-length/2, -width1/2, -height1/2);
485 glVertex3f(-length/2, width1/2, -height1/2);
486 glVertex3f( length/2, width2/2, -height2/2);
487 glVertex3f( length/2, -width2/2, -height2/2);
494 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
495 glVertex3f(-length/2, -width1/2, height1/2);
496 glVertex3f(-length/2, width1/2, height1/2);
497 glVertex3f( length/2, width2/2, height2/2);
498 glVertex3f( length/2, -width2/2, height2/2);
504 glNormal3f(0, -1, 0);
505 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
506 glVertex3f(-length/2, -width1/2, -height1/2);
507 glVertex3f(-length/2, -width1/2, height1/2);
508 glVertex3f( length/2, -width2/2, height2/2);
509 glVertex3f( length/2, -width2/2, -height2/2);
516 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
517 glVertex3f(-length/2, width1/2, -height1/2);
518 glVertex3f(-length/2, width1/2, height1/2);
519 glVertex3f( length/2, width2/2, height2/2);
520 glVertex3f( length/2, width2/2, -height2/2);
531 ctube (GLfloat diameter, GLfloat width, Bool wire)
536 32, True, True, wire);
541 armature (ModeInfo *mi)
543 gears_configuration *bp = &bps[MI_SCREEN(mi)];
544 int wire = MI_IS_WIREFRAME(mi);
546 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
547 GLfloat shiny = 128.0;
550 color[0] = 0.5 + frand(0.5);
551 color[1] = 0.5 + frand(0.5);
552 color[2] = 0.5 + frand(0.5);
555 bp->armature_polygons = 0;
557 bp->armature_dlist = glGenLists (1);
558 if (! bp->armature_dlist)
560 check_gl_error ("glGenLists");
564 glNewList (bp->armature_dlist, GL_COMPILE);
566 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
567 glMateriali (GL_FRONT, GL_SHININESS, shiny);
568 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
569 glColor3f (color[0], color[1], color[2]);
574 GLfloat s = bp->gears[0]->r * 2.7;
579 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
580 glRotatef (30, 0, 0, 1);
582 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
585 glTranslatef(0.0, 4.2, -1);
586 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
587 glTranslatef(0, 0, 1.8);
588 bp->armature_polygons += ctube (0.7, 0.7, wire);
592 glRotatef(120, 0.0, 0.0, 1.0);
593 glTranslatef(0.0, 4.2, -1);
594 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
595 glTranslatef(0, 0, 1.8);
596 bp->armature_polygons += ctube (0.7, 0.7, wire);
600 glRotatef(240, 0.0, 0.0, 1.0);
601 glTranslatef(0.0, 4.2, -1);
602 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
603 glTranslatef(0, 0, 1.8);
604 bp->armature_polygons += ctube (0.7, 0.7, wire);
607 glTranslatef(0, 0, 1.5); /* center disk */
608 bp->armature_polygons += ctube (1.5, 2, wire);
611 glRotatef(270, 0, 0, 1);
612 glRotatef(-10, 0, 1, 0);
613 glTranslatef(-2.2, 0, 0);
614 bp->armature_polygons += arm (4.0, 1.0, 0.5,
615 2.0, 1.0, wire); /* arm 1 */
619 glRotatef(30, 0, 0, 1);
620 glRotatef(-10, 0, 1, 0);
621 glTranslatef(-2.2, 0, 0);
622 bp->armature_polygons += arm (4.0, 1.0, 0.5,
623 2.0, 1.0, wire); /* arm 2 */
627 glRotatef(150, 0, 0, 1);
628 glRotatef(-10, 0, 1, 0);
629 glTranslatef(-2.2, 0, 0);
630 bp->armature_polygons += arm (4.0, 1.0, 0.5,
631 2.0, 1.0, wire); /* arm 3 */
641 planetary_gears (ModeInfo *mi)
643 gears_configuration *bp = &bps[MI_SCREEN(mi)];
644 gear *g0, *g1, *g2, *g3, *g4;
645 GLfloat distance = 2.02;
647 bp->planetary_p = True;
649 g0 = new_gear (mi, 0);
650 g1 = new_gear (mi, 0);
651 g2 = new_gear (mi, 0);
652 g3 = new_gear (mi, 0);
653 g4 = new_gear (mi, 0);
655 if (! place_gear (mi, g0, 0)) abort();
656 if (! place_gear (mi, g1, 0)) abort();
657 if (! place_gear (mi, g2, 0)) abort();
658 if (! place_gear (mi, g3, 0)) abort();
659 if (! place_gear (mi, g4, 0)) abort();
661 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
662 g0->tooth_w = g0->r / g0->nteeth;
663 g0->tooth_h = g0->tooth_w * 2.8;
665 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
682 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
683 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
685 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
686 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
688 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
689 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
695 /* rotate central gear 1/2 tooth-size if odd number of teeth */
697 g4->th -= (180.0 / g4->nteeth);
699 g0->inverted_p = True;
702 g0->nteeth = g1->nteeth * 3;
703 g0->r = g1->r * 3.05;
704 g0->inner_r = g0->r * 0.8;
707 g0->th = g1->th + (180 / g0->nteeth);
708 g0->ratio = g1->ratio / 3;
713 g0->size = INVOLUTE_LARGE;
715 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
718 bp->gears[bp->ngears++] = g1;
719 bp->gears[bp->ngears++] = g2;
720 bp->gears[bp->ngears++] = g3;
721 bp->gears[bp->ngears++] = g4;
722 bp->gears[bp->ngears++] = g0;
729 init_gears (ModeInfo *mi)
731 gears_configuration *bp;
732 int wire = MI_IS_WIREFRAME(mi);
736 bps = (gears_configuration *)
737 calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
739 fprintf(stderr, "%s: out of memory\n", progname);
743 bp = &bps[MI_SCREEN(mi)];
746 bp = &bps[MI_SCREEN(mi)];
748 bp->glx_context = init_GL(mi);
750 reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
754 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
755 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
756 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
757 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
759 glEnable(GL_LIGHTING);
761 glEnable(GL_DEPTH_TEST);
762 glEnable(GL_CULL_FACE);
764 glLightfv(GL_LIGHT0, GL_POSITION, pos);
765 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
766 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
767 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
771 double spin_speed = 0.5;
772 double wander_speed = 0.01;
773 double spin_accel = 0.25;
775 bp->rot = make_rotator (do_spin ? spin_speed : 0,
776 do_spin ? spin_speed : 0,
777 do_spin ? spin_speed : 0,
779 do_wander ? wander_speed : 0,
782 bp->trackball = gltrackball_init ();
787 planetary_gears (mi);
792 int total_gears = MI_COUNT (mi);
794 if (total_gears <= 0)
795 total_gears = 3 + abs (BELLRAND (8) - 4); /* 3 - 7, mostly 3. */
797 bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
800 for (i = 0; i < total_gears; i++)
801 g = place_new_gear (mi, g);
805 /* Center gears in scene. */
807 GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
809 for (i = 0; i < bp->ngears; i++)
811 gear *g = bp->gears[i];
812 if (g->x - g->r < minx) minx = g->x - g->r;
813 if (g->x + g->r > maxx) maxx = g->x + g->r;
814 if (g->y - g->r < miny) miny = g->y - g->r;
815 if (g->y + g->r > maxy) maxy = g->y + g->r;
823 /* Now render each gear into its display list.
825 for (i = 0; i < bp->ngears; i++)
827 gear *g = bp->gears[i];
828 g->dlist = glGenLists (1);
831 check_gl_error ("glGenLists");
835 glNewList (g->dlist, GL_COMPILE);
836 g->polygons += draw_involute_gear (g, wire);
845 draw_gears (ModeInfo *mi)
847 gears_configuration *bp = &bps[MI_SCREEN(mi)];
848 Display *dpy = MI_DISPLAY(mi);
849 Window window = MI_WINDOW(mi);
852 if (!bp->glx_context)
855 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
857 glShadeModel(GL_SMOOTH);
859 glEnable(GL_DEPTH_TEST);
860 glEnable(GL_NORMALIZE);
861 glEnable(GL_CULL_FACE);
863 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
869 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
870 glTranslatef ((x - 0.5) * 4,
874 gltrackball_rotate (bp->trackball);
876 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
878 /* add a little rotation for -no-spin mode */
882 glRotatef (x * 360, 1.0, 0.0, 0.0);
883 glRotatef (y * 360, 0.0, 1.0, 0.0);
884 glRotatef (z * 360, 0.0, 0.0, 1.0);
887 /* Center the scene's bounding box in the window,
891 GLfloat w = bp->bbox.x2 - bp->bbox.x1;
892 GLfloat h = bp->bbox.y2 - bp->bbox.y1;
893 GLfloat s = 10.0 / (w > h ? w : h);
895 glTranslatef (-(bp->bbox.x1 + w/2),
896 -(bp->bbox.y1 + h/2),
900 mi->polygon_count = 0;
902 for (i = 0; i < bp->ngears; i++)
904 gear *g = bp->gears[i];
908 glTranslatef (g->x, g->y, g->z);
909 glRotatef (g->th, 0, 0, 1);
911 glCallList (g->dlist);
912 mi->polygon_count += g->polygons;
919 glCallList (bp->armature_dlist);
920 mi->polygon_count += bp->armature_polygons;
926 if (!bp->button_down_p)
927 for (i = 0; i < bp->ngears; i++)
929 gear *g = bp->gears[i];
930 double off = g->ratio * 5 * speed;
937 if (mi->fps_p) do_fps (mi);
940 glXSwapBuffers(dpy, window);
943 XSCREENSAVER_MODULE ("Gears", gears)