1 /* pinion, Copyright (c) 2004-2011 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
12 #define DEFAULTS "*delay: 15000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
15 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
16 "*titleFont2: -*-helvetica-medium-r-normal-*-120-*\n" \
17 "*titleFont3: -*-helvetica-medium-r-normal-*-80-*\n" \
19 # define refresh_pinion 0
20 # define release_pinion 0
22 #define countof(x) (sizeof((x))/sizeof((*x)))
25 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
27 #include "xlockmore.h"
29 #include "gltrackball.h"
34 #ifdef USE_GL /* whole file */
36 #define DEF_SPIN_SPEED "1.0"
37 #define DEF_SCROLL_SPEED "1.0"
38 #define DEF_GEAR_SIZE "1.0"
39 #define DEF_MAX_RPM "900"
42 GLXContext *glx_context;
43 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
44 GLfloat vp_width, vp_height;
45 GLfloat render_left, render_right; /* area in which gears are displayed */
46 GLfloat layout_left, layout_right; /* layout region, on the right side */
52 trackball_state *trackball;
54 unsigned long mouse_gear_id;
56 XFontStruct *xfont1, *xfont2, *xfont3;
57 GLuint font1_dlist, font2_dlist, font3_dlist;
61 GLfloat plane_displacement; /* distance between coaxial gears */
63 int debug_size_failures; /* for debugging messages */
64 int debug_position_failures;
65 unsigned long current_length; /* gear count in current train */
66 unsigned long current_blur_length; /* how long have we been blurring? */
68 } pinion_configuration;
71 static pinion_configuration *pps = NULL;
73 /* command line arguments */
74 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
76 static Bool verbose_p = False; /* print progress on stderr */
77 static Bool debug_p = False; /* render as flat schematic */
79 /* internal debugging variables */
80 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
81 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
84 static XrmOptionDescRec opts[] = {
85 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
86 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
87 { "-size", ".gearSize", XrmoptionSepArg, 0 },
88 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
89 { "-debug", ".debug", XrmoptionNoArg, "True" },
90 { "-verbose",".verbose", XrmoptionNoArg, "True" },
93 static argtype vars[] = {
94 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
95 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
96 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
97 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
98 {&debug_p, "debug", "Debug", "False", t_Bool},
99 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
102 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
109 load_fonts (ModeInfo *mi)
111 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
112 load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
113 load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
114 load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
119 static void rpm_string (double rpm, char *s);
122 new_label (ModeInfo *mi)
124 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
129 if (pp->mouse_gear_id)
130 for (i = 0; i < pp->ngears; i++)
131 if (pp->gears[i]->id == pp->mouse_gear_id)
141 sprintf (label, "%d teeth\n", (int) g->nteeth);
142 rpm_string (g->rpm, label + strlen(label));
144 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
146 (g->size == INVOLUTE_SMALL ? "small" :
147 g->size == INVOLUTE_MEDIUM ? "medium"
149 g->tooth_h * MI_HEIGHT(mi));
152 glNewList (pp->title_list, GL_COMPILE);
157 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
158 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
159 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
160 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
162 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
164 glColor3f (0.8, 0.8, 0);
165 print_gl_string (mi->dpy, f, fl,
166 mi->xgwa.width, mi->xgwa.height,
167 10, mi->xgwa.height - 10,
178 /* Find the gear in the scene that is farthest to the right or left.
181 farthest_gear (ModeInfo *mi, Bool left_p)
183 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
186 double x = (left_p ? 999999 : -999999);
187 for (i = 0; i < pp->ngears; i++)
189 gear *g = pp->gears[i];
190 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
191 if (left_p ? (x > gx) : (x < gx))
201 /* Compute the revolutions per minute of a gear.
204 compute_rpm (ModeInfo *mi, gear *g)
206 double fps, rpf, rps;
207 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
209 if (fps > 150) fps = 150; /* let's be reasonable... */
210 if (fps < 10) fps = 10;
212 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
213 rps = rpf * fps; /* rotations per second */
217 /* Prints the RPM into a string, doing fancy float formatting.
220 rpm_string (double rpm, char *s)
224 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
225 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
226 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
227 else sprintf (buf, "%.16f",rpm);
230 while (buf[L-1] == '0') buf[--L] = 0;
231 if (buf[L-1] == '.') buf[--L] = 0;
242 /* Create and return a new gear sized for placement next to or on top of
243 the given parent gear (if any.) Returns 0 if out of memory.
246 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
248 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
249 gear *g = (gear *) calloc (1, sizeof (*g));
251 static unsigned long id = 0; /* only used in debugging output */
254 if (coaxial_p && !parent) abort();
257 g->coax_displacement = pp->plane_displacement;
262 if (loop_count > 1000)
263 /* The only time we loop in here is when making a coaxial gear, and
264 trying to pick a radius that is either significantly larger or
265 smaller than its parent. That shouldn't be hard, so something
266 must be really wrong if we can't do that in only a few tries.
270 /* Pick the size of the teeth.
272 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
274 g->tooth_w = parent->tooth_w;
275 g->tooth_h = parent->tooth_h;
276 g->thickness = parent->thickness;
277 g->thickness2 = parent->thickness2;
278 g->thickness3 = parent->thickness3;
280 else /* gears that begin trains get any size they want */
282 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
283 g->tooth_w = 0.007 * scale;
284 g->tooth_h = 0.005 * scale;
285 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
286 g->thickness2 = g->thickness / 4;
287 g->thickness3 = g->thickness;
290 /* Pick the number of teeth, and thus, the radius.
296 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
298 if (g->nteeth < 7 && (random() % 5) != 0)
299 goto AGAIN; /* Let's make very small tooth-counts more rare */
301 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
302 g->r = c / (M_PI * 2); /* c = 2 pi r */
308 if (! coaxial_p) break; /* yes */
309 if (g->nteeth == parent->nteeth) continue; /* ugly */
310 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
311 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
314 /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
318 g->color[0] = 0.5 + frand(0.5);
319 g->color[1] = 0.5 + frand(0.5);
320 g->color[2] = 0.5 + frand(0.5);
323 g->color2[0] = g->color[0] * 0.85;
324 g->color2[1] = g->color[1] * 0.85;
325 g->color2[2] = g->color[2] * 0.85;
326 g->color2[3] = g->color[3];
329 /* Decide on shape of gear interior:
330 - just a ring with teeth;
331 - that, plus a thinner in-set "plate" in the middle;
332 - that, plus a thin raised "lip" on the inner plate;
333 - or, a wide lip (really, a thicker third inner plate.)
335 if ((random() % 10) == 0)
337 /* inner_r can go all the way in; there's no inset disc. */
338 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
344 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
345 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
346 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
349 if (g->inner_r2 > (g->r * 0.2))
351 int nn = (random() % 10);
353 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
354 else if (nn <= 7 && g->inner_r2 >= 0.1)
355 g->inner_r3 = g->inner_r2 - 0.01;
359 /* Coaxial gears need to have the same innermost hole size (for the axle.)
360 Use whichever of the two is smaller. (Modifies parent.)
364 double hole1 = (g->inner_r3 ? g->inner_r3 :
365 g->inner_r2 ? g->inner_r2 :
367 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
368 parent->inner_r2 ? parent->inner_r2 :
370 double hole = (hole1 < hole2 ? hole1 : hole2);
371 if (hole <= 0) abort();
373 if (g->inner_r3) g->inner_r3 = hole;
374 else if (g->inner_r2) g->inner_r2 = hole;
375 else g->inner_r = hole;
377 if (parent->inner_r3) parent->inner_r3 = hole;
378 else if (parent->inner_r2) parent->inner_r2 = hole;
379 else parent->inner_r = hole;
382 /* If we have three discs, sometimes make the middle disc be spokes.
384 if (g->inner_r3 && ((random() % 5) == 0))
386 g->spokes = 2 + BELLRAND (5);
387 g->spoke_thickness = 1 + frand(7.0);
388 if (g->spokes == 2 && g->spoke_thickness < 2)
389 g->spoke_thickness += 1;
392 /* Sometimes add little nubbly bits, if there is room.
397 involute_biggest_ring (g, 0, &size, 0);
398 if (size > g->r * 0.2 && (random() % 5) == 0)
400 g->nubs = 1 + (random() % 16);
401 if (g->nubs > 8) g->nubs = 1;
405 if (g->inner_r3 > g->inner_r2) abort();
406 if (g->inner_r2 > g->inner_r) abort();
407 if (g->inner_r > g->r) abort();
409 /* Decide how complex the polygon model should be.
412 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
413 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
414 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
415 else g->size = INVOLUTE_LARGE;
424 /* Given a newly-created gear, place it next to its parent in the scene,
425 with its teeth meshed and the proper velocity. Returns False if it
426 didn't work. (Call this a bunch of times until either it works, or
427 you decide it's probably not going to.)
430 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
432 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
434 /* If this gear takes up more than 1/3rd of the screen, it's no good.
436 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
437 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
439 if (verbose_p && debug_placement_p && 0)
441 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
443 (g->r + g->tooth_h), gear_size,
444 pp->vp_width, pp->vp_height);
445 pp->debug_size_failures++;
449 /* Compute this gear's velocity.
453 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
454 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
458 g->ratio = parent->ratio; /* bound gears have the same ratio */
460 g->rpm = parent->rpm;
461 g->wobble = parent->wobble;
465 /* Gearing ratio is the ratio of the number of teeth to previous gear
466 (which is also the ratio of the circumferences.)
468 g->ratio = (double) parent->nteeth / (double) g->nteeth;
470 /* Set our initial rotation to match that of the previous gear,
471 multiplied by the gearing ratio. (This is finessed later,
472 once we know the exact position of the gear relative to its
475 g->th = -(parent->th * g->ratio);
477 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
479 double off = (180.0 / g->nteeth);
486 /* ratios are cumulative for all gears in the train. */
487 g->ratio *= parent->ratio;
491 /* Place the gear relative to the parent.
496 gear *rg = farthest_gear (mi, False);
497 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
498 if (right < pp->layout_left) /* place off screen */
499 right = pp->layout_left;
501 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
505 if (debug_one_gear_p)
510 double off = pp->plane_displacement;
514 g->z = parent->z + (g->r > parent->r /* small gear on top */
517 if (parent->r > g->r) /* mark which is top and which is bottom */
521 parent->wobble = 0; /* looks bad when axle moves */
530 g->coax_thickness = parent->thickness;
531 parent->coax_thickness = g->thickness;
533 /* Don't let the train get too close to or far from the screen.
534 If we're getting too close, give up on this gear.
535 (But getting very far away is fine.)
537 if (g->z >= off * 4 ||
540 if (verbose_p && debug_placement_p)
541 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
543 pp->debug_position_failures++;
547 else /* position it somewhere next to the parent. */
549 double r_off = parent->r + g->r;
552 if ((random() % 3) != 0)
553 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
555 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
557 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
558 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
561 /* If the angle we picked would have positioned this gear
562 more than halfway off screen, that's no good. */
563 if (g->y > pp->vp_top ||
564 g->y < pp->vp_bottom)
566 if (verbose_p && debug_placement_p)
567 fprintf (stderr, "%s: placement: out of bounds: %s\n",
568 progname, (g->y > pp->vp_top ? "top" : "bottom"));
569 pp->debug_position_failures++;
573 /* avoid accidentally changing sign of "th" in the math below. */
574 g->th += (g->th > 0 ? 360 : -360);
576 /* Adjust the rotation of the gear so that its teeth line up with its
577 parent, based on the position of the gear and the current rotation
581 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
582 double g_c = 2 * M_PI * g->r; /* circumference of g */
584 double p_t = p_c * (angle/360.0); /* distance travelled along
585 circumference of parent when
586 moving "angle" degrees along
588 double g_rat = p_t / g_c; /* if travelling that distance
589 along circumference of g,
590 ratio of g's circumference
592 double g_th = 360.0 * g_rat; /* that ratio in degrees */
594 g->th += angle + g_th;
598 if (debug_one_gear_p)
604 /* If the position we picked for this gear would cause it to already
605 be visible on the screen, give up. This can happen when the train
606 is growing backwards, and we don't want to see gears flash into
609 if (g->x - g->r - g->tooth_h < pp->render_right)
611 if (verbose_p && debug_placement_p)
612 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
613 pp->debug_position_failures++;
617 /* If the position we picked for this gear causes it to overlap
618 with any earlier gear in the train, give up.
623 for (i = pp->ngears-1; i >= 0; i--)
625 gear *og = pp->gears[i];
627 if (og == g) continue;
628 if (og == parent) continue;
629 if (g->z != og->z) continue; /* Ignore unless on same layer */
631 /* Collision detection without sqrt:
632 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
633 d < r1 + r2 d^2 < (r1 + r2)^2
635 if (((g->x - og->x) * (g->x - og->x) +
636 (g->y - og->y) * (g->y - og->y)) <
637 ((g->r + g->tooth_h + og->r + og->tooth_h) *
638 (g->r + g->tooth_h + og->r + og->tooth_h)))
640 if (verbose_p && debug_placement_p)
641 fprintf (stderr, "%s: placement: collision with %lu\n",
643 pp->debug_position_failures++;
652 /* Make deeper gears be darker.
655 double depth = g->z / pp->plane_displacement;
656 double brightness = 1 + (depth / 6);
658 if (brightness < limit) brightness = limit;
659 if (brightness > 1/limit) brightness = 1/limit;
660 g->color[0] *= brightness;
661 g->color[1] *= brightness;
662 g->color[2] *= brightness;
663 g->color2[0] *= brightness;
664 g->color2[1] *= brightness;
665 g->color2[2] *= brightness;
668 /* If a single frame of animation would cause the gear to rotate by
669 more than 1/2 the size of a single tooth, then it won't look right:
670 the gear will appear to be turning at some lower harmonic of its
674 double ratio = g->ratio * spin_speed;
675 double blur_limit = 180.0 / g->nteeth;
677 if (ratio > blur_limit)
678 g->motion_blur_p = 1;
682 /* ride until the wheels fall off... */
683 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
684 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
685 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
686 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
687 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
688 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
699 glDeleteLists (g->dlist, 1);
704 /* Make a new gear, place it next to its parent in the scene,
705 with its teeth meshed and the proper velocity. Returns the gear;
706 or 0 if it didn't work. (Call this a bunch of times until either
707 it works, or you decide it's probably not going to.)
710 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
712 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
719 if (loop_count >= 100)
727 g = new_gear (mi, parent, coaxial_p);
728 if (!g) return 0; /* out of memory? */
730 if (place_gear (mi, g, parent, coaxial_p))
736 /* We got a gear, and it is properly positioned.
737 Insert it in the scene.
739 if (pp->ngears + 2 >= pp->gears_size)
741 pp->gears_size += 100;
742 pp->gears = (gear **) realloc (pp->gears,
743 pp->gears_size * sizeof (*pp->gears));
746 fprintf (stderr, "%s: out of memory (%d gears)\n",
747 progname, pp->gears_size);
751 pp->gears[pp->ngears++] = g;
756 static void delete_gear (ModeInfo *mi, gear *g);
759 push_gear (ModeInfo *mi)
761 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
763 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
765 Bool tried_coaxial_p = False;
766 Bool coaxial_p = False;
767 Bool last_ditch_coax_p = False;
770 pp->debug_size_failures = 0;
771 pp->debug_position_failures = 0;
775 if (loop_count > 100) abort(); /* we're doomed! */
779 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
780 reset things to a sane velocity.
782 10,000 RPM at 30 FPS = 5.5 rotations per frame.
783 1,000 RPM at 30 FPS = 0.5 rotations per frame.
784 600 RPM at 30 FPS = 3 frames per rotation.
785 200 RPM at 30 FPS = 9 frames per rotation.
786 100 RPM at 30 FPS = 18 frames per rotation.
787 50 RPM at 30 FPS = 36 frames per rotation.
788 10 RPM at 30 FPS = 3 sec per rotation.
789 1 RPM at 30 FPS = 30 sec per rotation.
790 .5 RPM at 30 FPS = 1 min per rotation.
791 .1 RPM at 30 FPS = 5 min per rotation.
793 if (parent && parent->rpm > max_rpm)
798 rpm_string (parent->rpm, buf);
799 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
804 /* If the last N gears we've placed have all been motion-blurred, then
805 it's a safe guess that we've wandered off into the woods and aren't
806 coming back. Bail on this train.
808 if (pp->current_blur_length >= 10)
811 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
817 /* Sometimes, try to make a coaxial gear.
819 if (parent && !parent->coax_p && (random() % 40) == 0)
821 tried_coaxial_p = True;
823 g = place_new_gear (mi, parent, coaxial_p);
826 /* Try to make a regular gear.
831 g = place_new_gear (mi, parent, coaxial_p);
834 /* If we couldn't make a regular gear, then try to make a coxial gear
835 (unless we already tried that.)
837 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
839 tried_coaxial_p = True;
841 g = place_new_gear (mi, parent, coaxial_p);
843 last_ditch_coax_p = True;
846 /* If we couldn't do that either, then the train has hit a dead end:
853 fprintf (stderr, "%s: dead end!\n\n", progname);
855 g = place_new_gear (mi, parent, coaxial_p);
860 /* Unable to make/place any gears at all!
861 This can happen if we've backed ourself into a corner very near
862 the top-right or bottom-right corner of the growth zone.
863 It's time to add a gear, but there's no room to add one!
864 In that case, let's just wipe all the gears that are in the
865 growth zone and try again.
869 if (verbose_p && debug_placement_p)
871 "%s: placement: resetting growth zone! "
872 "failed: %d size, %d pos\n",
874 pp->debug_size_failures, pp->debug_position_failures);
875 for (i = pp->ngears-1; i >= 0; i--)
877 gear *g = pp->gears[i];
878 if (g->x - g->r - g->tooth_h < pp->render_left)
886 if (!parent) abort();
887 if (g->x != parent->x) abort();
888 if (g->y != parent->y) abort();
889 if (g->z == parent->z) abort();
890 if (g->r == parent->r) abort();
891 if (g->th != parent->th) abort();
892 if (g->ratio != parent->ratio) abort();
893 if (g->rpm != parent->rpm) abort();
898 fprintf (stderr, "%s: %5lu ", progname, g->id);
900 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
901 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
902 g->coax_p ? '1' : ' '),
905 fprintf (stderr, " %2d%%",
906 (int) (g->r * 2 * 100 / pp->vp_height));
907 fprintf (stderr, " %2d teeth", (int) g->nteeth);
908 fprintf (stderr, " %3.0f rpm;", g->rpm);
911 char buf1[50], buf2[50], buf3[100];
912 *buf1 = 0; *buf2 = 0; *buf3 = 0;
913 if (pp->debug_size_failures)
914 sprintf (buf1, "%3d sz", pp->debug_size_failures);
915 if (pp->debug_position_failures)
916 sprintf (buf2, "%2d pos", pp->debug_position_failures);
918 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
919 fprintf (stderr, "%-21s", buf3);
922 if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
923 fprintf (stderr, "\n");
927 pp->current_length = 1;
929 pp->current_length++;
931 if (g->motion_blur_p)
932 pp->current_blur_length++;
934 pp->current_blur_length = 0;
939 /* Remove the given gear from the scene and free it.
942 delete_gear (ModeInfo *mi, gear *g)
944 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
947 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
948 if (pp->gears[i] == g) break;
949 if (pp->gears[i] != g) abort();
951 for (; i < pp->ngears-1; i++) /* pull later entries forward */
952 pp->gears[i] = pp->gears[i+1];
959 /* Update the position of each gear in the scene.
962 scroll_gears (ModeInfo *mi)
964 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
967 for (i = 0; i < pp->ngears; i++)
968 pp->gears[i]->x -= (scroll_speed * 0.002);
970 /* if the right edge of any gear is off screen to the left, delete it.
972 for (i = pp->ngears-1; i >= 0; i--)
974 gear *g = pp->gears[i];
975 if (g->x + g->r + g->tooth_h < pp->render_left)
979 /* if the right edge of the last-added gear is left of the right edge
980 of the layout area, add another gear.
985 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
986 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
991 if (debug_one_gear_p) break;
995 if (i > 1 && verbose_p)
996 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1001 /* Update the rotation of each gear in the scene.
1004 spin_gears (ModeInfo *mi)
1006 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1009 for (i = 0; i < pp->ngears; i++)
1011 gear *g = pp->gears[i];
1012 double off = (g->ratio * spin_speed);
1022 /* Run the animation fast (without displaying anything) until the first
1023 gear is just about to come on screen. This is to avoid a big delay
1024 with a blank screen when -scroll is low.
1029 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1030 if (debug_one_gear_p) return;
1033 gear *g = farthest_gear (mi, True);
1034 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1042 /* Render one gear in the proper position, creating the gear's
1043 display list first if necessary.
1046 draw_gear (ModeInfo *mi, int which)
1048 Bool wire_p = MI_IS_WIREFRAME(mi);
1049 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1050 gear *g = pp->gears[which];
1053 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1054 g->x - g->r - g->tooth_h <= pp->render_right);
1056 if (!visible_p && !debug_p)
1061 g->dlist = glGenLists (1);
1064 /* I don't know how many display lists a GL implementation
1065 is supposed to provide, but hopefully it's more than
1066 "a few hundred", or we'll be in trouble...
1068 check_gl_error ("glGenLists");
1072 glNewList (g->dlist, GL_COMPILE);
1073 g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
1079 glTranslatef (g->x, g->y, g->z);
1081 if (g->motion_blur_p && !pp->button_down_p)
1083 /* If we're in motion-blur mode, then draw the gear so that each
1084 frame rotates it by exactly one half tooth-width, so that it
1085 looks flickery around the edges. But, revert to the normal
1086 way when the mouse button is down lest the user see overlapping
1089 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1095 glRotatef (th, 0, 0, 1);
1100 mi->polygon_count += draw_involute_schematic (g, wire_p);
1103 glCallList (g->dlist);
1104 mi->polygon_count += g->polygons;
1112 /* Render all gears.
1115 draw_gears (ModeInfo *mi)
1117 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1118 Bool wire_p = MI_IS_WIREFRAME(mi);
1121 glColor4f (1, 1, 0.8, 1);
1125 for (i = 0; i < pp->ngears; i++)
1128 /* draw a line connecting gears that are, uh, geared. */
1131 static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
1133 GLfloat ox=0, oy=0, oz=0;
1135 if (!wire_p) glDisable(GL_LIGHTING);
1136 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
1137 glColor3f (color[0], color[1], color[2]);
1139 for (i = 0; i < pp->ngears; i++)
1141 gear *g = pp->gears[i];
1142 glBegin(GL_LINE_STRIP);
1143 glVertex3f (g->x, g->y, g->z - off);
1144 glVertex3f (g->x, g->y, g->z + off);
1145 if (i > 0 && !g->base_p)
1146 glVertex3f (ox, oy, oz + off);
1152 if (!wire_p) glEnable(GL_LIGHTING);
1157 /* Mouse hit detection
1160 find_mouse_gear (ModeInfo *mi)
1162 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1163 int screen_width = MI_WIDTH (mi);
1164 int screen_height = MI_HEIGHT (mi);
1165 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1169 pp->mouse_gear_id = 0;
1171 /* Poll mouse position */
1176 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1177 &r, &c, &rx, &ry, &x, &y, &m);
1180 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1181 return; /* out of window */
1183 /* Run OpenGL hit detector */
1188 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
1189 glRenderMode (GL_SELECT);
1190 glMatrixMode (GL_PROJECTION);
1193 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
1194 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1195 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
1196 glMatrixMode (GL_MODELVIEW);
1198 draw_gears (mi); /* render into "select" buffer */
1200 glMatrixMode (GL_PROJECTION); /* restore old vp */
1202 glMatrixMode (GL_MODELVIEW);
1204 hits = glRenderMode (GL_RENDER); /* done selecting */
1209 GLuint name_count = 0;
1210 GLuint *p = (GLuint *) selbuf;
1214 for (i = 0; i < hits; i++)
1217 if (*p < min_z) /* find match closest to screen */
1226 if (name_count > 0) /* take first hit */
1227 pp->mouse_gear_id = pnames[0];
1233 /* Window management, etc
1236 reshape_pinion (ModeInfo *mi, int width, int height)
1238 GLfloat h = (GLfloat) height / (GLfloat) width;
1239 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1241 glViewport (0, 0, (GLint) width, (GLint) height);
1243 glMatrixMode(GL_PROJECTION);
1245 gluPerspective (30.0, 1/h, 1.0, 100.0);
1247 glMatrixMode(GL_MODELVIEW);
1249 gluLookAt( 0.0, 0.0, 30.0,
1253 glClear(GL_COLOR_BUFFER_BIT);
1256 GLfloat render_width, layout_width;
1257 pp->vp_height = 1.0;
1260 pp->vp_left = -pp->vp_width/2;
1261 pp->vp_right = pp->vp_width/2;
1262 pp->vp_top = pp->vp_height/2;
1263 pp->vp_bottom = -pp->vp_height/2;
1265 render_width = pp->vp_width * 2;
1266 layout_width = pp->vp_width * 0.8 * gear_size;
1268 pp->render_left = -render_width/2;
1269 pp->render_right = render_width/2;
1271 pp->layout_left = pp->render_right;
1272 pp->layout_right = pp->layout_left + layout_width;
1278 pinion_handle_event (ModeInfo *mi, XEvent *event)
1280 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1282 if (event->xany.type == ButtonPress &&
1283 event->xbutton.button == Button1)
1285 pp->button_down_p = True;
1286 gltrackball_start (pp->trackball,
1287 event->xbutton.x, event->xbutton.y,
1288 MI_WIDTH (mi), MI_HEIGHT (mi));
1291 else if (event->xany.type == ButtonRelease &&
1292 event->xbutton.button == Button1)
1294 pp->button_down_p = False;
1297 else if (event->xany.type == ButtonPress &&
1298 (event->xbutton.button == Button4 ||
1299 event->xbutton.button == Button5 ||
1300 event->xbutton.button == Button6 ||
1301 event->xbutton.button == Button7))
1303 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
1304 !!event->xbutton.state);
1307 else if (event->xany.type == MotionNotify &&
1310 gltrackball_track (pp->trackball,
1311 event->xmotion.x, event->xmotion.y,
1312 MI_WIDTH (mi), MI_HEIGHT (mi));
1315 else if (event->xany.type == KeyPress)
1319 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1320 if (c == ' ' && debug_one_gear_p && pp->ngears)
1322 delete_gear (mi, pp->gears[0]);
1332 init_pinion (ModeInfo *mi)
1334 pinion_configuration *pp;
1335 int wire = MI_IS_WIREFRAME(mi);
1338 pps = (pinion_configuration *)
1339 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1341 fprintf(stderr, "%s: out of memory\n", progname);
1346 pp = &pps[MI_SCREEN(mi)];
1348 pp->glx_context = init_GL(mi);
1351 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1352 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1354 pp->title_list = glGenLists (1);
1360 pp->plane_displacement = gear_size * 0.1;
1364 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1365 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1366 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1367 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1369 glEnable(GL_LIGHTING);
1370 glEnable(GL_LIGHT0);
1371 glEnable(GL_DEPTH_TEST);
1372 glEnable(GL_CULL_FACE);
1374 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1375 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1376 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1377 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1380 pp->trackball = gltrackball_init ();
1387 draw_pinion (ModeInfo *mi)
1389 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1390 Display *dpy = MI_DISPLAY(mi);
1391 Window window = MI_WINDOW(mi);
1392 Bool wire_p = MI_IS_WIREFRAME(mi);
1394 if (!pp->glx_context)
1397 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1399 if (!pp->button_down_p)
1401 if (!debug_one_gear_p || pp->ngears == 0)
1406 glShadeModel(GL_SMOOTH);
1408 glEnable(GL_DEPTH_TEST);
1409 glEnable(GL_NORMALIZE);
1410 glEnable(GL_CULL_FACE);
1412 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1416 gltrackball_rotate (pp->trackball);
1417 mi->polygon_count = 0;
1419 glScalef (16, 16, 16); /* map vp_width/height to the screen */
1421 if (debug_one_gear_p) /* zoom in */
1423 else if (debug_p) /* show the "visible" and "layout" areas */
1425 GLfloat ow = pp->layout_right - pp->render_left;
1426 GLfloat rw = pp->render_right - pp->render_left;
1427 GLfloat s = (pp->vp_width / ow) * 0.85;
1429 glTranslatef (-(ow - rw) / 2, 0, 0);
1434 glScalef (s, s, s); /* zoom in a little more */
1435 glRotatef (-35, 1, 0, 0); /* tilt back */
1436 glRotatef ( 8, 0, 1, 0); /* tilt left */
1437 glTranslatef (0.02, 0.1, 0); /* pan up */
1444 if (!wire_p) glDisable(GL_LIGHTING);
1445 glColor3f (0.6, 0, 0);
1446 glBegin(GL_LINE_LOOP);
1447 glVertex3f (pp->render_left, pp->vp_top, 0);
1448 glVertex3f (pp->render_right, pp->vp_top, 0);
1449 glVertex3f (pp->render_right, pp->vp_bottom, 0);
1450 glVertex3f (pp->render_left, pp->vp_bottom, 0);
1452 glColor3f (0.4, 0, 0);
1454 glVertex3f (pp->vp_left, pp->vp_top, 0);
1455 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
1456 glVertex3f (pp->vp_right, pp->vp_top, 0);
1457 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
1459 glColor3f (0, 0.4, 0);
1460 glBegin(GL_LINE_LOOP);
1461 glVertex3f (pp->layout_left, pp->vp_top, 0);
1462 glVertex3f (pp->layout_right, pp->vp_top, 0);
1463 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1464 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
1466 if (!wire_p) glEnable(GL_LIGHTING);
1469 if (pp->draw_tick++ > 10) /* only do this every N frames */
1472 find_mouse_gear (mi);
1478 glCallList (pp->title_list);
1480 if (mi->fps_p) do_fps (mi);
1483 glXSwapBuffers(dpy, window);
1486 XSCREENSAVER_MODULE ("Pinion", pinion)