1 /* pinion, Copyright (c) 2004-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
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 (g->x != parent->x) abort();
887 if (g->y != parent->y) abort();
888 if (g->z == parent->z) abort();
889 if (g->r == parent->r) abort();
890 if (g->th != parent->th) abort();
891 if (g->ratio != parent->ratio) abort();
892 if (g->rpm != parent->rpm) abort();
897 fprintf (stderr, "%s: %5lu ", progname, g->id);
899 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
900 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
901 g->coax_p ? '1' : ' '),
904 fprintf (stderr, " %2d%%",
905 (int) (g->r * 2 * 100 / pp->vp_height));
906 fprintf (stderr, " %2d teeth", (int) g->nteeth);
907 fprintf (stderr, " %3.0f rpm;", g->rpm);
910 char buf1[50], buf2[50], buf3[100];
911 *buf1 = 0; *buf2 = 0; *buf3 = 0;
912 if (pp->debug_size_failures)
913 sprintf (buf1, "%3d sz", pp->debug_size_failures);
914 if (pp->debug_position_failures)
915 sprintf (buf2, "%2d pos", pp->debug_position_failures);
917 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
918 fprintf (stderr, "%-21s", buf3);
921 if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
922 fprintf (stderr, "\n");
926 pp->current_length = 1;
928 pp->current_length++;
930 if (g->motion_blur_p)
931 pp->current_blur_length++;
933 pp->current_blur_length = 0;
938 /* Remove the given gear from the scene and free it.
941 delete_gear (ModeInfo *mi, gear *g)
943 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
946 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
947 if (pp->gears[i] == g) break;
948 if (pp->gears[i] != g) abort();
950 for (; i < pp->ngears-1; i++) /* pull later entries forward */
951 pp->gears[i] = pp->gears[i+1];
958 /* Update the position of each gear in the scene.
961 scroll_gears (ModeInfo *mi)
963 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
966 for (i = 0; i < pp->ngears; i++)
967 pp->gears[i]->x -= (scroll_speed * 0.002);
969 /* if the right edge of any gear is off screen to the left, delete it.
971 for (i = pp->ngears-1; i >= 0; i--)
973 gear *g = pp->gears[i];
974 if (g->x + g->r + g->tooth_h < pp->render_left)
978 /* if the right edge of the last-added gear is left of the right edge
979 of the layout area, add another gear.
984 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
985 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
990 if (debug_one_gear_p) break;
994 if (i > 1 && verbose_p)
995 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1000 /* Update the rotation of each gear in the scene.
1003 spin_gears (ModeInfo *mi)
1005 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1008 for (i = 0; i < pp->ngears; i++)
1010 gear *g = pp->gears[i];
1011 double off = (g->ratio * spin_speed);
1021 /* Run the animation fast (without displaying anything) until the first
1022 gear is just about to come on screen. This is to avoid a big delay
1023 with a blank screen when -scroll is low.
1028 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1029 if (debug_one_gear_p) return;
1032 gear *g = farthest_gear (mi, True);
1033 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1041 /* Render one gear in the proper position, creating the gear's
1042 display list first if necessary.
1045 draw_gear (ModeInfo *mi, int which)
1047 Bool wire_p = MI_IS_WIREFRAME(mi);
1048 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1049 gear *g = pp->gears[which];
1052 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1053 g->x - g->r - g->tooth_h <= pp->render_right);
1055 if (!visible_p && !debug_p)
1060 g->dlist = glGenLists (1);
1063 /* I don't know how many display lists a GL implementation
1064 is supposed to provide, but hopefully it's more than
1065 "a few hundred", or we'll be in trouble...
1067 check_gl_error ("glGenLists");
1071 glNewList (g->dlist, GL_COMPILE);
1072 g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
1078 glTranslatef (g->x, g->y, g->z);
1080 if (g->motion_blur_p && !pp->button_down_p)
1082 /* If we're in motion-blur mode, then draw the gear so that each
1083 frame rotates it by exactly one half tooth-width, so that it
1084 looks flickery around the edges. But, revert to the normal
1085 way when the mouse button is down lest the user see overlapping
1088 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1094 glRotatef (th, 0, 0, 1);
1099 mi->polygon_count += draw_involute_schematic (g, wire_p);
1102 glCallList (g->dlist);
1103 mi->polygon_count += g->polygons;
1111 /* Render all gears.
1114 draw_gears (ModeInfo *mi)
1116 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1117 Bool wire_p = MI_IS_WIREFRAME(mi);
1120 glColor4f (1, 1, 0.8, 1);
1124 for (i = 0; i < pp->ngears; i++)
1127 /* draw a line connecting gears that are, uh, geared. */
1130 static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
1132 GLfloat ox=0, oy=0, oz=0;
1134 if (!wire_p) glDisable(GL_LIGHTING);
1135 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
1136 glColor3f (color[0], color[1], color[2]);
1138 for (i = 0; i < pp->ngears; i++)
1140 gear *g = pp->gears[i];
1141 glBegin(GL_LINE_STRIP);
1142 glVertex3f (g->x, g->y, g->z - off);
1143 glVertex3f (g->x, g->y, g->z + off);
1144 if (i > 0 && !g->base_p)
1145 glVertex3f (ox, oy, oz + off);
1151 if (!wire_p) glEnable(GL_LIGHTING);
1156 /* Mouse hit detection
1159 find_mouse_gear (ModeInfo *mi)
1161 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1162 int screen_width = MI_WIDTH (mi);
1163 int screen_height = MI_HEIGHT (mi);
1164 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1168 pp->mouse_gear_id = 0;
1170 /* Poll mouse position */
1175 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1176 &r, &c, &rx, &ry, &x, &y, &m);
1179 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1180 return; /* out of window */
1182 /* Run OpenGL hit detector */
1187 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
1188 glRenderMode (GL_SELECT);
1189 glMatrixMode (GL_PROJECTION);
1192 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
1193 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1194 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
1195 glMatrixMode (GL_MODELVIEW);
1197 draw_gears (mi); /* render into "select" buffer */
1199 glMatrixMode (GL_PROJECTION); /* restore old vp */
1201 glMatrixMode (GL_MODELVIEW);
1203 hits = glRenderMode (GL_RENDER); /* done selecting */
1208 GLuint name_count = 0;
1209 GLuint *p = (GLuint *) selbuf;
1213 for (i = 0; i < hits; i++)
1216 if (*p < min_z) /* find match closest to screen */
1225 if (name_count > 0) /* take first hit */
1226 pp->mouse_gear_id = pnames[0];
1232 /* Window management, etc
1235 reshape_pinion (ModeInfo *mi, int width, int height)
1237 GLfloat h = (GLfloat) height / (GLfloat) width;
1238 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1240 glViewport (0, 0, (GLint) width, (GLint) height);
1242 glMatrixMode(GL_PROJECTION);
1244 gluPerspective (30.0, 1/h, 1.0, 100.0);
1246 glMatrixMode(GL_MODELVIEW);
1248 gluLookAt( 0.0, 0.0, 30.0,
1252 glClear(GL_COLOR_BUFFER_BIT);
1255 GLfloat render_width, layout_width;
1256 pp->vp_height = 1.0;
1259 pp->vp_left = -pp->vp_width/2;
1260 pp->vp_right = pp->vp_width/2;
1261 pp->vp_top = pp->vp_height/2;
1262 pp->vp_bottom = -pp->vp_height/2;
1264 render_width = pp->vp_width * 2;
1265 layout_width = pp->vp_width * 0.8 * gear_size;
1267 pp->render_left = -render_width/2;
1268 pp->render_right = render_width/2;
1270 pp->layout_left = pp->render_right;
1271 pp->layout_right = pp->layout_left + layout_width;
1277 pinion_handle_event (ModeInfo *mi, XEvent *event)
1279 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1281 if (event->xany.type == ButtonPress &&
1282 event->xbutton.button == Button1)
1284 pp->button_down_p = True;
1285 gltrackball_start (pp->trackball,
1286 event->xbutton.x, event->xbutton.y,
1287 MI_WIDTH (mi), MI_HEIGHT (mi));
1290 else if (event->xany.type == ButtonRelease &&
1291 event->xbutton.button == Button1)
1293 pp->button_down_p = False;
1296 else if (event->xany.type == ButtonPress &&
1297 (event->xbutton.button == Button4 ||
1298 event->xbutton.button == Button5 ||
1299 event->xbutton.button == Button6 ||
1300 event->xbutton.button == Button7))
1302 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
1303 !!event->xbutton.state);
1306 else if (event->xany.type == MotionNotify &&
1309 gltrackball_track (pp->trackball,
1310 event->xmotion.x, event->xmotion.y,
1311 MI_WIDTH (mi), MI_HEIGHT (mi));
1314 else if (event->xany.type == KeyPress)
1318 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1319 if (c == ' ' && debug_one_gear_p && pp->ngears)
1321 delete_gear (mi, pp->gears[0]);
1331 init_pinion (ModeInfo *mi)
1333 pinion_configuration *pp;
1334 int wire = MI_IS_WIREFRAME(mi);
1337 pps = (pinion_configuration *)
1338 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1340 fprintf(stderr, "%s: out of memory\n", progname);
1344 pp = &pps[MI_SCREEN(mi)];
1347 pp = &pps[MI_SCREEN(mi)];
1349 pp->glx_context = init_GL(mi);
1352 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
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)