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 g->size = INVOLUTE_LARGE;
250 /* Given a newly-created gear, place it next to its parent in the scene,
251 with its teeth meshed and the proper velocity. Returns False if it
252 didn't work. (Call this a bunch of times until either it works, or
253 you decide it's probably not going to.)
254 [Mostly lifted from pinion.c]
257 place_gear (ModeInfo *mi, gear *g, gear *parent)
259 gears_configuration *bp = &bps[MI_SCREEN(mi)];
261 /* Compute this gear's velocity.
265 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
266 g->th = 1; /* not 0 */
270 /* Gearing ratio is the ratio of the number of teeth to previous gear
271 (which is also the ratio of the circumferences.)
273 g->ratio = (double) parent->nteeth / (double) g->nteeth;
275 /* Set our initial rotation to match that of the previous gear,
276 multiplied by the gearing ratio. (This is finessed later,
277 once we know the exact position of the gear relative to its
280 g->th = -(parent->th * g->ratio);
282 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
284 double off = (180.0 / g->nteeth);
291 /* ratios are cumulative for all gears in the train. */
292 g->ratio *= parent->ratio;
296 if (parent) /* Place the gear next to the parent. */
298 double r_off = parent->r + g->r;
301 angle = (random() % 360) - 180; /* -180 to +180 degrees */
303 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
304 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
307 /* avoid accidentally changing sign of "th" in the math below. */
308 g->th += (g->th > 0 ? 360 : -360);
310 /* Adjust the rotation of the gear so that its teeth line up with its
311 parent, based on the position of the gear and the current rotation
315 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
316 double g_c = 2 * M_PI * g->r; /* circumference of g */
318 double p_t = p_c * (angle/360.0); /* distance travelled along
319 circumference of parent when
320 moving "angle" degrees along
322 double g_rat = p_t / g_c; /* if travelling that distance
323 along circumference of g,
324 ratio of g's circumference
326 double g_th = 360.0 * g_rat; /* that ratio in degrees */
328 g->th += angle + g_th;
332 /* If the position we picked for this gear causes it to overlap
333 with any earlier gear in the train, give up.
338 for (i = bp->ngears-1; i >= 0; i--)
340 gear *og = bp->gears[i];
342 if (og == g) continue;
343 if (og == parent) continue;
344 if (g->z != og->z) continue; /* Ignore unless on same layer */
346 /* Collision detection without sqrt:
347 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
348 d < r1 + r2 d^2 < (r1 + r2)^2
350 if (((g->x - og->x) * (g->x - og->x) +
351 (g->y - og->y) * (g->y - og->y)) <
352 ((g->r + g->tooth_h + og->r + og->tooth_h) *
353 (g->r + g->tooth_h + og->r + og->tooth_h)))
362 /* Make a new gear, place it next to its parent in the scene,
363 with its teeth meshed and the proper velocity. Returns the gear;
364 or 0 if it didn't work. (Call this a bunch of times until either
365 it works, or you decide it's probably not going to.)
366 [Mostly lifted from pinion.c]
369 place_new_gear (ModeInfo *mi, gear *parent)
371 gears_configuration *bp = &bps[MI_SCREEN(mi)];
378 if (loop_count >= 100)
386 g = new_gear (mi, parent);
387 if (!g) return 0; /* out of memory? */
389 if (place_gear (mi, g, parent))
395 /* We got a gear, and it is properly positioned.
396 Insert it in the scene.
398 bp->gears[bp->ngears++] = g;
405 GLfloat width1, GLfloat height1,
406 GLfloat width2, GLfloat height2,
410 glShadeModel(GL_FLAT);
412 #if 0 /* don't need these - they're embedded in other objects */
415 glNormal3f(-1, 0, 0);
416 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
417 glVertex3f(-length/2, -width1/2, -height1/2);
418 glVertex3f(-length/2, width1/2, -height1/2);
419 glVertex3f(-length/2, width1/2, height1/2);
420 glVertex3f(-length/2, -width1/2, height1/2);
427 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
428 glVertex3f(length/2, -width2/2, -height2/2);
429 glVertex3f(length/2, width2/2, -height2/2);
430 glVertex3f(length/2, width2/2, height2/2);
431 glVertex3f(length/2, -width2/2, height2/2);
438 glNormal3f(0, 0, -1);
439 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
440 glVertex3f(-length/2, -width1/2, -height1/2);
441 glVertex3f(-length/2, width1/2, -height1/2);
442 glVertex3f( length/2, width2/2, -height2/2);
443 glVertex3f( length/2, -width2/2, -height2/2);
450 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
451 glVertex3f(-length/2, -width1/2, height1/2);
452 glVertex3f(-length/2, width1/2, height1/2);
453 glVertex3f( length/2, width2/2, height2/2);
454 glVertex3f( length/2, -width2/2, height2/2);
460 glNormal3f(0, -1, 0);
461 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
462 glVertex3f(-length/2, -width1/2, -height1/2);
463 glVertex3f(-length/2, -width1/2, height1/2);
464 glVertex3f( length/2, -width2/2, height2/2);
465 glVertex3f( length/2, -width2/2, -height2/2);
472 glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
473 glVertex3f(-length/2, width1/2, -height1/2);
474 glVertex3f(-length/2, width1/2, height1/2);
475 glVertex3f( length/2, width2/2, height2/2);
476 glVertex3f( length/2, width2/2, -height2/2);
487 ctube (GLfloat diameter, GLfloat width, Bool wire)
492 32, True, True, wire);
497 armature (ModeInfo *mi)
499 gears_configuration *bp = &bps[MI_SCREEN(mi)];
500 int wire = MI_IS_WIREFRAME(mi);
502 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
503 GLfloat shiny = 128.0;
506 color[0] = 0.5 + frand(0.5);
507 color[1] = 0.5 + frand(0.5);
508 color[2] = 0.5 + frand(0.5);
511 bp->armature_polygons = 0;
513 bp->armature_dlist = glGenLists (1);
514 if (! bp->armature_dlist)
516 check_gl_error ("glGenLists");
520 glNewList (bp->armature_dlist, GL_COMPILE);
522 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
523 glMateriali (GL_FRONT, GL_SHININESS, shiny);
524 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
525 glColor3f (color[0], color[1], color[2]);
530 GLfloat s = bp->gears[0]->r * 2.7;
535 glTranslatef (0, 0, 1.4 + bp->gears[0]->thickness);
536 glRotatef (30, 0, 0, 1);
538 bp->armature_polygons += ctube (0.5, 10, wire); /* center axle */
541 glTranslatef(0.0, 4.2, -1);
542 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 1 */
543 glTranslatef(0, 0, 1.8);
544 bp->armature_polygons += ctube (0.7, 0.7, wire);
548 glRotatef(120, 0.0, 0.0, 1.0);
549 glTranslatef(0.0, 4.2, -1);
550 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 2 */
551 glTranslatef(0, 0, 1.8);
552 bp->armature_polygons += ctube (0.7, 0.7, wire);
556 glRotatef(240, 0.0, 0.0, 1.0);
557 glTranslatef(0.0, 4.2, -1);
558 bp->armature_polygons += ctube (0.5, 3, wire); /* axle 3 */
559 glTranslatef(0, 0, 1.8);
560 bp->armature_polygons += ctube (0.7, 0.7, wire);
563 glTranslatef(0, 0, 1.5); /* center disk */
564 bp->armature_polygons += ctube (1.5, 2, wire);
567 glRotatef(270, 0, 0, 1);
568 glRotatef(-10, 0, 1, 0);
569 glTranslatef(-2.2, 0, 0);
570 bp->armature_polygons += arm (4.0, 1.0, 0.5,
571 2.0, 1.0, wire); /* arm 1 */
575 glRotatef(30, 0, 0, 1);
576 glRotatef(-10, 0, 1, 0);
577 glTranslatef(-2.2, 0, 0);
578 bp->armature_polygons += arm (4.0, 1.0, 0.5,
579 2.0, 1.0, wire); /* arm 2 */
583 glRotatef(150, 0, 0, 1);
584 glRotatef(-10, 0, 1, 0);
585 glTranslatef(-2.2, 0, 0);
586 bp->armature_polygons += arm (4.0, 1.0, 0.5,
587 2.0, 1.0, wire); /* arm 3 */
597 planetary_gears (ModeInfo *mi)
599 gears_configuration *bp = &bps[MI_SCREEN(mi)];
600 gear *g0, *g1, *g2, *g3, *g4;
601 GLfloat distance = 2.02;
603 bp->planetary_p = True;
605 g0 = new_gear (mi, 0);
606 g1 = new_gear (mi, 0);
607 g2 = new_gear (mi, 0);
608 g3 = new_gear (mi, 0);
609 g4 = new_gear (mi, 0);
611 if (! place_gear (mi, g0, 0)) abort();
612 if (! place_gear (mi, g1, 0)) abort();
613 if (! place_gear (mi, g2, 0)) abort();
614 if (! place_gear (mi, g3, 0)) abort();
615 if (! place_gear (mi, g4, 0)) abort();
617 g0->nteeth = 12 + (3 * (random() % 10)); /* must be multiple of 3 */
618 g0->tooth_w = g0->r / g0->nteeth;
619 g0->tooth_h = g0->tooth_w * 2.8;
621 # define COPY(F) g4->F = g3->F = g2->F = g1->F = g0->F
638 g1->x = cos (M_PI * 2 / 3) * g1->r * distance;
639 g1->y = sin (M_PI * 2 / 3) * g1->r * distance;
641 g2->x = cos (M_PI * 4 / 3) * g2->r * distance;
642 g2->y = sin (M_PI * 4 / 3) * g2->r * distance;
644 g3->x = cos (M_PI * 6 / 3) * g3->r * distance;
645 g3->y = sin (M_PI * 6 / 3) * g3->r * distance;
651 /* rotate central gear 1/2 tooth-size if odd number of teeth */
653 g4->th -= (180.0 / g4->nteeth);
655 g0->inverted_p = True;
658 g0->nteeth = g1->nteeth * 3;
659 g0->r = g1->r * 3.05;
660 g0->inner_r = g0->r * 0.8;
663 g0->th = g1->th + (180 / g0->nteeth);
664 g0->ratio = g1->ratio / 3;
669 g0->size = INVOLUTE_LARGE;
671 bp->gears = (gear **) calloc (6, sizeof(**bp->gears));
674 bp->gears[bp->ngears++] = g1;
675 bp->gears[bp->ngears++] = g2;
676 bp->gears[bp->ngears++] = g3;
677 bp->gears[bp->ngears++] = g4;
678 bp->gears[bp->ngears++] = g0;
685 init_gears (ModeInfo *mi)
687 gears_configuration *bp;
688 int wire = MI_IS_WIREFRAME(mi);
692 bps = (gears_configuration *)
693 calloc (MI_NUM_SCREENS(mi), sizeof (gears_configuration));
695 fprintf(stderr, "%s: out of memory\n", progname);
700 bp = &bps[MI_SCREEN(mi)];
702 bp->glx_context = init_GL(mi);
704 reshape_gears (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
708 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
709 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
710 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
711 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
713 glEnable(GL_LIGHTING);
715 glEnable(GL_DEPTH_TEST);
716 glEnable(GL_CULL_FACE);
718 glLightfv(GL_LIGHT0, GL_POSITION, pos);
719 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
720 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
721 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
726 double spin_speed = 0.5;
727 double wander_speed = 0.01;
728 double spin_accel = 0.25;
730 bp->rot = make_rotator (do_spin ? spin_speed : 0,
731 do_spin ? spin_speed : 0,
732 do_spin ? spin_speed : 0,
734 do_wander ? wander_speed : 0,
737 bp->trackball = gltrackball_init (True);
742 for (i = 0; i < bp->ngears; i++)
743 free_gear (bp->gears[i]);
751 planetary_gears (mi);
756 int total_gears = MI_COUNT (mi);
758 bp->planetary_p = False;
760 if (total_gears <= 0)
761 total_gears = 3 + abs (BELLRAND (8) - 4); /* 3 - 7, mostly 3. */
762 bp->gears = (gear **) calloc (total_gears+2, sizeof(**bp->gears));
765 for (i = 0; i < total_gears; i++)
766 g = place_new_gear (mi, g);
770 /* Center gears in scene. */
772 GLfloat minx=99999, miny=99999, maxx=-99999, maxy=-99999;
774 for (i = 0; i < bp->ngears; i++)
776 gear *g = bp->gears[i];
777 if (g->x - g->r < minx) minx = g->x - g->r;
778 if (g->x + g->r > maxx) maxx = g->x + g->r;
779 if (g->y - g->r < miny) miny = g->y - g->r;
780 if (g->y + g->r > maxy) maxy = g->y + g->r;
788 /* Now render each gear into its display list.
790 for (i = 0; i < bp->ngears; i++)
792 gear *g = bp->gears[i];
793 g->dlist = glGenLists (1);
796 check_gl_error ("glGenLists");
800 glNewList (g->dlist, GL_COMPILE);
801 g->polygons += draw_involute_gear (g, wire);
810 draw_gears (ModeInfo *mi)
812 gears_configuration *bp = &bps[MI_SCREEN(mi)];
813 Display *dpy = MI_DISPLAY(mi);
814 Window window = MI_WINDOW(mi);
817 if (!bp->glx_context)
820 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
822 glShadeModel(GL_SMOOTH);
824 glEnable(GL_DEPTH_TEST);
825 glEnable(GL_NORMALIZE);
826 glEnable(GL_CULL_FACE);
828 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
834 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
835 glTranslatef ((x - 0.5) * 4,
839 gltrackball_rotate (bp->trackball);
841 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
843 /* add a little rotation for -no-spin mode */
847 glRotatef (x * 360, 1.0, 0.0, 0.0);
848 glRotatef (y * 360, 0.0, 1.0, 0.0);
849 glRotatef (z * 360, 0.0, 0.0, 1.0);
852 /* Center the scene's bounding box in the window,
856 GLfloat w = bp->bbox.x2 - bp->bbox.x1;
857 GLfloat h = bp->bbox.y2 - bp->bbox.y1;
858 GLfloat s = 10.0 / (w > h ? w : h);
860 glTranslatef (-(bp->bbox.x1 + w/2),
861 -(bp->bbox.y1 + h/2),
865 mi->polygon_count = 0;
867 for (i = 0; i < bp->ngears; i++)
869 gear *g = bp->gears[i];
873 glTranslatef (g->x, g->y, g->z);
874 glRotatef (g->th, 0, 0, 1);
876 glCallList (g->dlist);
877 mi->polygon_count += g->polygons;
884 glCallList (bp->armature_dlist);
885 mi->polygon_count += bp->armature_polygons;
891 if (!bp->button_down_p)
892 for (i = 0; i < bp->ngears; i++)
894 gear *g = bp->gears[i];
895 double off = g->ratio * 5 * speed;
902 if (mi->fps_p) do_fps (mi);
905 glXSwapBuffers(dpy, window);
909 gears_handle_event (ModeInfo *mi, XEvent *event)
911 gears_configuration *bp = &bps[MI_SCREEN(mi)];
913 if (gltrackball_event_handler (event, bp->trackball,
914 MI_WIDTH (mi), MI_HEIGHT (mi),
917 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
926 XSCREENSAVER_MODULE ("Gears", gears)