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" \
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;
91 if (width > height * 5) { /* tiny window: show middle */
92 height = width * 9/16;
94 h = height / (GLfloat) width;
97 glViewport (0, y, (GLint) width, (GLint) height);
99 glMatrixMode(GL_PROJECTION);
101 gluPerspective (30.0, 1/h, 1.0, 100.0);
103 glMatrixMode(GL_MODELVIEW);
105 gluLookAt( 0.0, 0.0, 30.0,
109 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
111 int o = (int) current_device_rotation();
112 if (o != 0 && o != 180 && o != -180)
113 glScalef (1/h, 1/h, 1/h);
117 glClear(GL_COLOR_BUFFER_BIT);
125 glDeleteLists (g->dlist, 1);
130 /* Create and return a new gear sized for placement next to or on top of
131 the given parent gear (if any.) Returns 0 if out of memory.
132 [Mostly lifted from pinion.c]
135 new_gear (ModeInfo *mi, gear *parent)
137 gears_configuration *bp = &bps[MI_SCREEN(mi)];
138 gear *g = (gear *) calloc (1, sizeof (*g));
139 static unsigned long id = 0; /* only used in debugging output */
144 /* Pick the size of the teeth.
146 if (parent) /* adjascent gears need matching teeth */
148 g->tooth_w = parent->tooth_w;
149 g->tooth_h = parent->tooth_h;
150 g->tooth_slope = -parent->tooth_slope;
152 else /* gears that begin trains get any size they want */
154 g->tooth_w = 0.007 * (1.0 + BELLRAND(4.0));
155 g->tooth_h = 0.005 * (1.0 + BELLRAND(8.0));
157 g->tooth_slope = ((random() % 8)
159 : 0.5 + BELLRAND(1));
163 /* Pick the number of teeth, and thus, the radius.
168 if (!parent || bp->ngears > 4)
169 g->nteeth = 5 + BELLRAND (20);
171 g->nteeth = parent->nteeth * (0.5 + BELLRAND(2));
173 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
174 g->r = c / (M_PI * 2); /* c = 2 pi r */
177 g->thickness = g->tooth_w + frand (g->r);
178 g->thickness2 = g->thickness * 0.7;
179 g->thickness3 = g->thickness;
183 g->color[0] = 0.5 + frand(0.5);
184 g->color[1] = 0.5 + frand(0.5);
185 g->color[2] = 0.5 + frand(0.5);
188 g->color2[0] = g->color[0] * 0.85;
189 g->color2[1] = g->color[1] * 0.85;
190 g->color2[2] = g->color[2] * 0.85;
191 g->color2[3] = g->color[3];
194 /* Decide on shape of gear interior:
195 - just a ring with teeth;
196 - that, plus a thinner in-set "plate" in the middle;
197 - that, plus a thin raised "lip" on the inner plate;
198 - or, a wide lip (really, a thicker third inner plate.)
200 if ((random() % 10) == 0)
202 /* inner_r can go all the way in; there's no inset disc. */
203 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
209 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
210 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
211 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
214 if (g->inner_r2 > (g->r * 0.2))
216 int nn = (random() % 10);
218 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
219 else if (nn <= 7 && g->inner_r2 >= 0.1)
220 g->inner_r3 = g->inner_r2 - 0.01;
224 /* If we have three discs, sometimes make the middle disc be spokes.
226 if (g->inner_r3 && ((random() % 5) == 0))
228 g->spokes = 2 + BELLRAND (5);
229 g->spoke_thickness = 1 + frand(7.0);
230 if (g->spokes == 2 && g->spoke_thickness < 2)
231 g->spoke_thickness += 1;
234 /* Sometimes add little nubbly bits, if there is room.
239 involute_biggest_ring (g, 0, &size, 0);
240 if (size > g->r * 0.2 && (random() % 5) == 0)
242 g->nubs = 1 + (random() % 16);
243 if (g->nubs > 8) g->nubs = 1;
247 if (g->inner_r3 > g->inner_r2) abort();
248 if (g->inner_r2 > g->inner_r) abort();
249 if (g->inner_r > g->r) abort();
251 /* Decide how complex the polygon model should be.
254 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
255 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
256 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
257 else if (pix <= 25) g->size = INVOLUTE_LARGE;
258 else g->size = INVOLUTE_HUGE;
267 /* Given a newly-created gear, place it next to its parent in the scene,
268 with its teeth meshed and the proper velocity. Returns False if it
269 didn't work. (Call this a bunch of times until either it works, or
270 you decide it's probably not going to.)
271 [Mostly lifted from pinion.c]
274 place_gear (ModeInfo *mi, gear *g, gear *parent)
276 gears_configuration *bp = &bps[MI_SCREEN(mi)];
278 /* Compute this gear's velocity.
282 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
283 g->th = 1; /* not 0 */
287 /* Gearing ratio is the ratio of the number of teeth to previous gear
288 (which is also the ratio of the circumferences.)
290 g->ratio = (double) parent->nteeth / (double) g->nteeth;
292 /* Set our initial rotation to match that of the previous gear,
293 multiplied by the gearing ratio. (This is finessed later,
294 once we know the exact position of the gear relative to its
297 g->th = -(parent->th * g->ratio);
299 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
301 double off = (180.0 / g->nteeth);
308 /* ratios are cumulative for all gears in the train. */
309 g->ratio *= parent->ratio;
313 if (parent) /* Place the gear next to the parent. */
315 double r_off = parent->r + g->r;
318 angle = (random() % 360) - 180; /* -180 to +180 degrees */
320 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
321 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
324 /* avoid accidentally changing sign of "th" in the math below. */
325 g->th += (g->th > 0 ? 360 : -360);
327 /* Adjust the rotation of the gear so that its teeth line up with its
328 parent, based on the position of the gear and the current rotation
332 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
333 double g_c = 2 * M_PI * g->r; /* circumference of g */
335 double p_t = p_c * (angle/360.0); /* distance travelled along
336 circumference of parent when
337 moving "angle" degrees along
339 double g_rat = p_t / g_c; /* if travelling that distance
340 along circumference of g,
341 ratio of g's circumference
343 double g_th = 360.0 * g_rat; /* that ratio in degrees */
345 g->th += angle + g_th;
349 /* If the position we picked for this gear causes it to overlap
350 with any earlier gear in the train, give up.
355 for (i = bp->ngears-1; i >= 0; i--)
357 gear *og = bp->gears[i];
359 if (og == g) continue;
360 if (og == parent) continue;
361 if (g->z != og->z) continue; /* Ignore unless on same layer */
363 /* Collision detection without sqrt:
364 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
365 d < r1 + r2 d^2 < (r1 + r2)^2
367 if (((g->x - og->x) * (g->x - og->x) +
368 (g->y - og->y) * (g->y - og->y)) <
369 ((g->r + g->tooth_h + og->r + og->tooth_h) *
370 (g->r + g->tooth_h + og->r + og->tooth_h)))
379 /* Make a new gear, place it next to its parent in the scene,
380 with its teeth meshed and the proper velocity. Returns the gear;
381 or 0 if it didn't work. (Call this a bunch of times until either
382 it works, or you decide it's probably not going to.)
383 [Mostly lifted from pinion.c]
386 place_new_gear (ModeInfo *mi, gear *parent)
388 gears_configuration *bp = &bps[MI_SCREEN(mi)];
395 if (loop_count >= 100)
403 g = new_gear (mi, parent);
404 if (!g) return 0; /* out of memory? */
406 if (place_gear (mi, g, parent))
412 /* We got a gear, and it is properly positioned.
413 Insert it in the scene.
415 bp->gears[bp->ngears++] = g;
422 GLfloat width1, GLfloat height1,
423 GLfloat width2, GLfloat height2,
427 glShadeModel(GL_FLAT);
429 #if 0 /* don't need these - they're embedded in other objects */
432 glNormal3f(-1, 0, 0);
433 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
434 glVertex3f(-length/2, -width1/2, -height1/2);
435 glVertex3f(-length/2, width1/2, -height1/2);
436 glVertex3f(-length/2, width1/2, height1/2);
437 glVertex3f(-length/2, -width1/2, height1/2);
444 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
445 glVertex3f(length/2, -width2/2, -height2/2);
446 glVertex3f(length/2, width2/2, -height2/2);
447 glVertex3f(length/2, width2/2, height2/2);
448 glVertex3f(length/2, -width2/2, height2/2);
455 glNormal3f(0, 0, -1);
456 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
457 glVertex3f(-length/2, -width1/2, -height1/2);
458 glVertex3f(-length/2, width1/2, -height1/2);
459 glVertex3f( length/2, width2/2, -height2/2);
460 glVertex3f( length/2, -width2/2, -height2/2);
467 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
468 glVertex3f(-length/2, -width1/2, height1/2);
469 glVertex3f(-length/2, width1/2, height1/2);
470 glVertex3f( length/2, width2/2, height2/2);
471 glVertex3f( length/2, -width2/2, height2/2);
477 glNormal3f(0, -1, 0);
478 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
479 glVertex3f(-length/2, -width1/2, -height1/2);
480 glVertex3f(-length/2, -width1/2, height1/2);
481 glVertex3f( length/2, -width2/2, height2/2);
482 glVertex3f( length/2, -width2/2, -height2/2);
489 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
490 glVertex3f(-length/2, width1/2, -height1/2);
491 glVertex3f(-length/2, width1/2, height1/2);
492 glVertex3f( length/2, width2/2, height2/2);
493 glVertex3f( length/2, width2/2, -height2/2);
504 ctube (GLfloat diameter, GLfloat width, Bool wire)
509 32, True, True, wire);
514 armature (ModeInfo *mi)
516 gears_configuration *bp = &bps[MI_SCREEN(mi)];
517 int wire = MI_IS_WIREFRAME(mi);
519 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
520 GLfloat shiny = 128.0;
523 color[0] = 0.5 + frand(0.5);
524 color[1] = 0.5 + frand(0.5);
525 color[2] = 0.5 + frand(0.5);
528 bp->armature_polygons = 0;
530 bp->armature_dlist = glGenLists (1);
531 if (! bp->armature_dlist)
533 check_gl_error ("glGenLists");
537 glNewList (bp->armature_dlist, GL_COMPILE);
539 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
540 glMateriali (GL_FRONT, GL_SHININESS, shiny);
541 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
542 glColor3f (color[0], color[1], color[2]);
547 GLfloat s = bp->gears[0]->r * 2.7;
552 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
553 glRotatef (30, 0, 0, 1);
555 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
558 glTranslatef(0.0, 4.2, -1);
559 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
560 glTranslatef(0, 0, 1.8);
561 bp->armature_polygons += ctube (0.7, 0.7, wire);
565 glRotatef(120, 0.0, 0.0, 1.0);
566 glTranslatef(0.0, 4.2, -1);
567 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
568 glTranslatef(0, 0, 1.8);
569 bp->armature_polygons += ctube (0.7, 0.7, wire);
573 glRotatef(240, 0.0, 0.0, 1.0);
574 glTranslatef(0.0, 4.2, -1);
575 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
576 glTranslatef(0, 0, 1.8);
577 bp->armature_polygons += ctube (0.7, 0.7, wire);
580 glTranslatef(0, 0, 1.5); /* center disk */
581 bp->armature_polygons += ctube (1.5, 2, wire);
584 glRotatef(270, 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 1 */
592 glRotatef(30, 0, 0, 1);
593 glRotatef(-10, 0, 1, 0);
594 glTranslatef(-2.2, 0, 0);
595 bp->armature_polygons += arm (4.0, 1.0, 0.5,
596 2.0, 1.0, wire); /* arm 2 */
600 glRotatef(150, 0, 0, 1);
601 glRotatef(-10, 0, 1, 0);
602 glTranslatef(-2.2, 0, 0);
603 bp->armature_polygons += arm (4.0, 1.0, 0.5,
604 2.0, 1.0, wire); /* arm 3 */
614 planetary_gears (ModeInfo *mi)
616 gears_configuration *bp = &bps[MI_SCREEN(mi)];
617 gear *g0, *g1, *g2, *g3, *g4;
618 GLfloat distance = 2.02;
620 bp->planetary_p = True;
622 g0 = new_gear (mi, 0);
623 g1 = new_gear (mi, 0);
624 g2 = new_gear (mi, 0);
625 g3 = new_gear (mi, 0);
626 g4 = new_gear (mi, 0);
628 if (! place_gear (mi, g0, 0)) abort();
629 if (! place_gear (mi, g1, 0)) abort();
630 if (! place_gear (mi, g2, 0)) abort();
631 if (! place_gear (mi, g3, 0)) abort();
632 if (! place_gear (mi, g4, 0)) abort();
634 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
635 g0->tooth_w = g0->r / g0->nteeth;
636 g0->tooth_h = g0->tooth_w * 2.8;
638 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
655 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
656 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
658 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
659 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
661 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
662 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
668 /* rotate central gear 1/2 tooth-size if odd number of teeth */
670 g4->th -= (180.0 / g4->nteeth);
672 g0->inverted_p = True;
675 g0->nteeth = g1->nteeth * 3;
676 g0->r = g1->r * 3.05;
677 g0->inner_r = g0->r * 0.8;
680 g0->th = g1->th + (180 / g0->nteeth);
681 g0->ratio = g1->ratio / 3;
686 g0->size = INVOLUTE_LARGE;
688 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
691 bp->gears[bp->ngears++] = g1;
692 bp->gears[bp->ngears++] = g2;
693 bp->gears[bp->ngears++] = g3;
694 bp->gears[bp->ngears++] = g4;
695 bp->gears[bp->ngears++] = g0;
702 init_gears (ModeInfo *mi)
704 gears_configuration *bp;
705 int wire = MI_IS_WIREFRAME(mi);
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)