1 /* pinion, Copyright (c) 2004-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
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 texture_font_data *font1, *font2, *font3;
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 pp->font1 = load_texture_font (mi->dpy, "titleFont");
113 pp->font2 = load_texture_font (mi->dpy, "titleFont2");
114 pp->font3 = load_texture_font (mi->dpy, "titleFont3");
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);
155 texture_font_data *fd;
156 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
158 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
163 glColor3f (0.8, 0.8, 0);
164 print_gl_string (mi->dpy, fd,
165 mi->xgwa.width, mi->xgwa.height,
166 10, mi->xgwa.height - 10,
177 /* Find the gear in the scene that is farthest to the right or left.
180 farthest_gear (ModeInfo *mi, Bool left_p)
182 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
185 double x = (left_p ? 999999 : -999999);
186 for (i = 0; i < pp->ngears; i++)
188 gear *g = pp->gears[i];
189 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
190 if (left_p ? (x > gx) : (x < gx))
200 /* Compute the revolutions per minute of a gear.
203 compute_rpm (ModeInfo *mi, gear *g)
205 double fps, rpf, rps;
206 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
208 if (fps > 150) fps = 150; /* let's be reasonable... */
209 if (fps < 10) fps = 10;
211 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
212 rps = rpf * fps; /* rotations per second */
216 /* Prints the RPM into a string, doing fancy float formatting.
219 rpm_string (double rpm, char *s)
223 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
224 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
225 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
226 else sprintf (buf, "%.16f",rpm);
229 while (buf[L-1] == '0') buf[--L] = 0;
230 if (buf[L-1] == '.') buf[--L] = 0;
241 /* Create and return a new gear sized for placement next to or on top of
242 the given parent gear (if any.) Returns 0 if out of memory.
245 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
247 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
248 gear *g = (gear *) calloc (1, sizeof (*g));
250 static unsigned long id = 0; /* only used in debugging output */
253 if (coaxial_p && !parent) abort();
256 g->coax_displacement = pp->plane_displacement;
261 if (loop_count > 1000)
262 /* The only time we loop in here is when making a coaxial gear, and
263 trying to pick a radius that is either significantly larger or
264 smaller than its parent. That shouldn't be hard, so something
265 must be really wrong if we can't do that in only a few tries.
269 /* Pick the size of the teeth.
271 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
273 g->tooth_w = parent->tooth_w;
274 g->tooth_h = parent->tooth_h;
275 g->thickness = parent->thickness;
276 g->thickness2 = parent->thickness2;
277 g->thickness3 = parent->thickness3;
279 else /* gears that begin trains get any size they want */
281 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
282 g->tooth_w = 0.007 * scale;
283 g->tooth_h = 0.005 * scale;
284 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
285 g->thickness2 = g->thickness / 4;
286 g->thickness3 = g->thickness;
289 /* Pick the number of teeth, and thus, the radius.
295 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
297 if (g->nteeth < 7 && (random() % 5) != 0)
298 goto AGAIN; /* Let's make very small tooth-counts more rare */
300 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
301 g->r = c / (M_PI * 2); /* c = 2 pi r */
307 if (! coaxial_p) break; /* yes */
308 if (g->nteeth == parent->nteeth) continue; /* ugly */
309 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
310 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
313 /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
317 g->color[0] = 0.5 + frand(0.5);
318 g->color[1] = 0.5 + frand(0.5);
319 g->color[2] = 0.5 + frand(0.5);
322 g->color2[0] = g->color[0] * 0.85;
323 g->color2[1] = g->color[1] * 0.85;
324 g->color2[2] = g->color[2] * 0.85;
325 g->color2[3] = g->color[3];
328 /* Decide on shape of gear interior:
329 - just a ring with teeth;
330 - that, plus a thinner in-set "plate" in the middle;
331 - that, plus a thin raised "lip" on the inner plate;
332 - or, a wide lip (really, a thicker third inner plate.)
334 if ((random() % 10) == 0)
336 /* inner_r can go all the way in; there's no inset disc. */
337 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
343 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
344 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
345 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
348 if (g->inner_r2 > (g->r * 0.2))
350 int nn = (random() % 10);
352 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
353 else if (nn <= 7 && g->inner_r2 >= 0.1)
354 g->inner_r3 = g->inner_r2 - 0.01;
358 /* Coaxial gears need to have the same innermost hole size (for the axle.)
359 Use whichever of the two is smaller. (Modifies parent.)
363 double hole1 = (g->inner_r3 ? g->inner_r3 :
364 g->inner_r2 ? g->inner_r2 :
366 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
367 parent->inner_r2 ? parent->inner_r2 :
369 double hole = (hole1 < hole2 ? hole1 : hole2);
370 if (hole <= 0) abort();
372 if (g->inner_r3) g->inner_r3 = hole;
373 else if (g->inner_r2) g->inner_r2 = hole;
374 else g->inner_r = hole;
376 if (parent->inner_r3) parent->inner_r3 = hole;
377 else if (parent->inner_r2) parent->inner_r2 = hole;
378 else parent->inner_r = hole;
381 /* If we have three discs, sometimes make the middle disc be spokes.
383 if (g->inner_r3 && ((random() % 5) == 0))
385 g->spokes = 2 + BELLRAND (5);
386 g->spoke_thickness = 1 + frand(7.0);
387 if (g->spokes == 2 && g->spoke_thickness < 2)
388 g->spoke_thickness += 1;
391 /* Sometimes add little nubbly bits, if there is room.
396 involute_biggest_ring (g, 0, &size, 0);
397 if (size > g->r * 0.2 && (random() % 5) == 0)
399 g->nubs = 1 + (random() % 16);
400 if (g->nubs > 8) g->nubs = 1;
404 if (g->inner_r3 > g->inner_r2) abort();
405 if (g->inner_r2 > g->inner_r) abort();
406 if (g->inner_r > g->r) abort();
408 /* Decide how complex the polygon model should be.
411 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
412 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
413 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
414 else g->size = INVOLUTE_LARGE;
423 /* Given a newly-created gear, place it next to its parent in the scene,
424 with its teeth meshed and the proper velocity. Returns False if it
425 didn't work. (Call this a bunch of times until either it works, or
426 you decide it's probably not going to.)
429 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
431 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
433 /* If this gear takes up more than 1/3rd of the screen, it's no good.
435 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
436 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
438 if (verbose_p && debug_placement_p && 0)
440 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
442 (g->r + g->tooth_h), gear_size,
443 pp->vp_width, pp->vp_height);
444 pp->debug_size_failures++;
448 /* Compute this gear's velocity.
452 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
453 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
457 g->ratio = parent->ratio; /* bound gears have the same ratio */
459 g->rpm = parent->rpm;
460 g->wobble = parent->wobble;
464 /* Gearing ratio is the ratio of the number of teeth to previous gear
465 (which is also the ratio of the circumferences.)
467 g->ratio = (double) parent->nteeth / (double) g->nteeth;
469 /* Set our initial rotation to match that of the previous gear,
470 multiplied by the gearing ratio. (This is finessed later,
471 once we know the exact position of the gear relative to its
474 g->th = -(parent->th * g->ratio);
476 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
478 double off = (180.0 / g->nteeth);
485 /* ratios are cumulative for all gears in the train. */
486 g->ratio *= parent->ratio;
490 /* Place the gear relative to the parent.
495 gear *rg = farthest_gear (mi, False);
496 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
497 if (right < pp->layout_left) /* place off screen */
498 right = pp->layout_left;
500 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
504 if (debug_one_gear_p)
509 double off = pp->plane_displacement;
513 g->z = parent->z + (g->r > parent->r /* small gear on top */
516 if (parent->r > g->r) /* mark which is top and which is bottom */
520 parent->wobble = 0; /* looks bad when axle moves */
529 g->coax_thickness = parent->thickness;
530 parent->coax_thickness = g->thickness;
532 /* Don't let the train get too close to or far from the screen.
533 If we're getting too close, give up on this gear.
534 (But getting very far away is fine.)
536 if (g->z >= off * 4 ||
539 if (verbose_p && debug_placement_p)
540 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
542 pp->debug_position_failures++;
546 else /* position it somewhere next to the parent. */
548 double r_off = parent->r + g->r;
551 if ((random() % 3) != 0)
552 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
554 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
556 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
557 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
560 /* If the angle we picked would have positioned this gear
561 more than halfway off screen, that's no good. */
562 if (g->y > pp->vp_top ||
563 g->y < pp->vp_bottom)
565 if (verbose_p && debug_placement_p)
566 fprintf (stderr, "%s: placement: out of bounds: %s\n",
567 progname, (g->y > pp->vp_top ? "top" : "bottom"));
568 pp->debug_position_failures++;
572 /* avoid accidentally changing sign of "th" in the math below. */
573 g->th += (g->th > 0 ? 360 : -360);
575 /* Adjust the rotation of the gear so that its teeth line up with its
576 parent, based on the position of the gear and the current rotation
580 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
581 double g_c = 2 * M_PI * g->r; /* circumference of g */
583 double p_t = p_c * (angle/360.0); /* distance travelled along
584 circumference of parent when
585 moving "angle" degrees along
587 double g_rat = p_t / g_c; /* if travelling that distance
588 along circumference of g,
589 ratio of g's circumference
591 double g_th = 360.0 * g_rat; /* that ratio in degrees */
593 g->th += angle + g_th;
597 if (debug_one_gear_p)
603 /* If the position we picked for this gear would cause it to already
604 be visible on the screen, give up. This can happen when the train
605 is growing backwards, and we don't want to see gears flash into
608 if (g->x - g->r - g->tooth_h < pp->render_right)
610 if (verbose_p && debug_placement_p)
611 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
612 pp->debug_position_failures++;
616 /* If the position we picked for this gear causes it to overlap
617 with any earlier gear in the train, give up.
622 for (i = pp->ngears-1; i >= 0; i--)
624 gear *og = pp->gears[i];
626 if (og == g) continue;
627 if (og == parent) continue;
628 if (g->z != og->z) continue; /* Ignore unless on same layer */
630 /* Collision detection without sqrt:
631 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
632 d < r1 + r2 d^2 < (r1 + r2)^2
634 if (((g->x - og->x) * (g->x - og->x) +
635 (g->y - og->y) * (g->y - og->y)) <
636 ((g->r + g->tooth_h + og->r + og->tooth_h) *
637 (g->r + g->tooth_h + og->r + og->tooth_h)))
639 if (verbose_p && debug_placement_p)
640 fprintf (stderr, "%s: placement: collision with %lu\n",
642 pp->debug_position_failures++;
651 /* Make deeper gears be darker.
654 double depth = g->z / pp->plane_displacement;
655 double brightness = 1 + (depth / 6);
657 if (brightness < limit) brightness = limit;
658 if (brightness > 1/limit) brightness = 1/limit;
659 g->color[0] *= brightness;
660 g->color[1] *= brightness;
661 g->color[2] *= brightness;
662 g->color2[0] *= brightness;
663 g->color2[1] *= brightness;
664 g->color2[2] *= brightness;
667 /* If a single frame of animation would cause the gear to rotate by
668 more than 1/2 the size of a single tooth, then it won't look right:
669 the gear will appear to be turning at some lower harmonic of its
673 double ratio = g->ratio * spin_speed;
674 double blur_limit = 180.0 / g->nteeth;
676 if (ratio > blur_limit)
677 g->motion_blur_p = 1;
681 /* ride until the wheels fall off... */
682 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
683 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
684 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
685 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
686 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
687 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
698 glDeleteLists (g->dlist, 1);
703 /* Make a new gear, place it next to its parent in the scene,
704 with its teeth meshed and the proper velocity. Returns the gear;
705 or 0 if it didn't work. (Call this a bunch of times until either
706 it works, or you decide it's probably not going to.)
709 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
711 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
718 if (loop_count >= 100)
726 g = new_gear (mi, parent, coaxial_p);
727 if (!g) return 0; /* out of memory? */
729 if (place_gear (mi, g, parent, coaxial_p))
735 /* We got a gear, and it is properly positioned.
736 Insert it in the scene.
738 if (pp->ngears + 2 >= pp->gears_size)
740 pp->gears_size += 100;
741 pp->gears = (gear **) realloc (pp->gears,
742 pp->gears_size * sizeof (*pp->gears));
745 fprintf (stderr, "%s: out of memory (%d gears)\n",
746 progname, pp->gears_size);
750 pp->gears[pp->ngears++] = g;
755 static void delete_gear (ModeInfo *mi, gear *g);
758 push_gear (ModeInfo *mi)
760 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
762 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
764 Bool tried_coaxial_p = False;
765 Bool coaxial_p = False;
766 Bool last_ditch_coax_p = False;
769 pp->debug_size_failures = 0;
770 pp->debug_position_failures = 0;
774 if (loop_count > 100) abort(); /* we're doomed! */
778 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
779 reset things to a sane velocity.
781 10,000 RPM at 30 FPS = 5.5 rotations per frame.
782 1,000 RPM at 30 FPS = 0.5 rotations per frame.
783 600 RPM at 30 FPS = 3 frames per rotation.
784 200 RPM at 30 FPS = 9 frames per rotation.
785 100 RPM at 30 FPS = 18 frames per rotation.
786 50 RPM at 30 FPS = 36 frames per rotation.
787 10 RPM at 30 FPS = 3 sec per rotation.
788 1 RPM at 30 FPS = 30 sec per rotation.
789 .5 RPM at 30 FPS = 1 min per rotation.
790 .1 RPM at 30 FPS = 5 min per rotation.
792 if (parent && parent->rpm > max_rpm)
797 rpm_string (parent->rpm, buf);
798 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
803 /* If the last N gears we've placed have all been motion-blurred, then
804 it's a safe guess that we've wandered off into the woods and aren't
805 coming back. Bail on this train.
807 if (pp->current_blur_length >= 10)
810 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
816 /* Sometimes, try to make a coaxial gear.
818 if (parent && !parent->coax_p && (random() % 40) == 0)
820 tried_coaxial_p = True;
822 g = place_new_gear (mi, parent, coaxial_p);
825 /* Try to make a regular gear.
830 g = place_new_gear (mi, parent, coaxial_p);
833 /* If we couldn't make a regular gear, then try to make a coxial gear
834 (unless we already tried that.)
836 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
838 tried_coaxial_p = True;
840 g = place_new_gear (mi, parent, coaxial_p);
842 last_ditch_coax_p = True;
845 /* If we couldn't do that either, then the train has hit a dead end:
852 fprintf (stderr, "%s: dead end!\n\n", progname);
854 g = place_new_gear (mi, parent, coaxial_p);
859 /* Unable to make/place any gears at all!
860 This can happen if we've backed ourself into a corner very near
861 the top-right or bottom-right corner of the growth zone.
862 It's time to add a gear, but there's no room to add one!
863 In that case, let's just wipe all the gears that are in the
864 growth zone and try again.
868 if (verbose_p && debug_placement_p)
870 "%s: placement: resetting growth zone! "
871 "failed: %d size, %d pos\n",
873 pp->debug_size_failures, pp->debug_position_failures);
874 for (i = pp->ngears-1; i >= 0; i--)
876 gear *g = pp->gears[i];
877 if (g->x - g->r - g->tooth_h < pp->render_left)
885 if (!parent) abort();
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)];
1163 # ifndef HAVE_JWZGLES
1165 int screen_width = MI_WIDTH (mi);
1166 int screen_height = MI_HEIGHT (mi);
1167 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1171 pp->mouse_gear_id = 0;
1173 /* Poll mouse position */
1178 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1179 &r, &c, &rx, &ry, &x, &y, &m);
1182 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1183 return; /* out of window */
1185 /* Run OpenGL hit detector */
1190 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
1191 glRenderMode (GL_SELECT);
1192 glMatrixMode (GL_PROJECTION);
1195 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
1196 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1197 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
1198 glMatrixMode (GL_MODELVIEW);
1200 draw_gears (mi); /* render into "select" buffer */
1202 glMatrixMode (GL_PROJECTION); /* restore old vp */
1204 glMatrixMode (GL_MODELVIEW);
1206 hits = glRenderMode (GL_RENDER); /* done selecting */
1211 GLuint name_count = 0;
1212 GLuint *p = (GLuint *) selbuf;
1216 for (i = 0; i < hits; i++)
1219 if (*p < min_z) /* find match closest to screen */
1228 if (name_count > 0) /* take first hit */
1229 pp->mouse_gear_id = pnames[0];
1233 #else /* HAVE_JWZGLES */
1234 /* #### not yet implemented */
1235 pp->mouse_gear_id = pp->gears[1]->id;
1237 #endif /* HAVE_JWZGLES */
1243 /* Window management, etc
1246 reshape_pinion (ModeInfo *mi, int width, int height)
1248 GLfloat h = (GLfloat) height / (GLfloat) width;
1249 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1251 glViewport (0, 0, (GLint) width, (GLint) height);
1253 glMatrixMode(GL_PROJECTION);
1255 gluPerspective (30.0, 1/h, 1.0, 100.0);
1257 glMatrixMode(GL_MODELVIEW);
1259 gluLookAt( 0.0, 0.0, 30.0,
1263 glClear(GL_COLOR_BUFFER_BIT);
1266 GLfloat render_width, layout_width;
1267 pp->vp_height = 1.0;
1270 pp->vp_left = -pp->vp_width/2;
1271 pp->vp_right = pp->vp_width/2;
1272 pp->vp_top = pp->vp_height/2;
1273 pp->vp_bottom = -pp->vp_height/2;
1275 render_width = pp->vp_width * 2;
1276 layout_width = pp->vp_width * 0.8 * gear_size;
1278 pp->render_left = -render_width/2;
1279 pp->render_right = render_width/2;
1281 pp->layout_left = pp->render_right;
1282 pp->layout_right = pp->layout_left + layout_width;
1288 pinion_handle_event (ModeInfo *mi, XEvent *event)
1290 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1292 if (gltrackball_event_handler (event, pp->trackball,
1293 MI_WIDTH (mi), MI_HEIGHT (mi),
1294 &pp->button_down_p))
1296 else if (event->xany.type == KeyPress)
1300 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1301 if (c == ' ' && debug_one_gear_p && pp->ngears)
1303 delete_gear (mi, pp->gears[0]);
1313 init_pinion (ModeInfo *mi)
1315 pinion_configuration *pp;
1318 pps = (pinion_configuration *)
1319 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1321 fprintf(stderr, "%s: out of memory\n", progname);
1326 pp = &pps[MI_SCREEN(mi)];
1328 pp->glx_context = init_GL(mi);
1331 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1332 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1334 pp->title_list = glGenLists (1);
1340 pp->plane_displacement = gear_size * 0.1;
1342 pp->trackball = gltrackball_init (False);
1349 draw_pinion (ModeInfo *mi)
1351 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1352 Display *dpy = MI_DISPLAY(mi);
1353 Window window = MI_WINDOW(mi);
1354 Bool wire_p = MI_IS_WIREFRAME(mi);
1356 if (!pp->glx_context)
1359 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1362 glRotatef(current_device_rotation(), 0, 0, 1);
1366 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1367 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1368 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1369 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1371 glEnable(GL_LIGHTING);
1372 glEnable(GL_LIGHT0);
1373 glEnable(GL_DEPTH_TEST);
1374 glEnable(GL_CULL_FACE);
1376 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1377 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1378 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1379 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1382 if (!pp->button_down_p)
1384 if (!debug_one_gear_p || pp->ngears == 0)
1389 glShadeModel(GL_SMOOTH);
1391 glEnable(GL_DEPTH_TEST);
1392 glEnable(GL_NORMALIZE);
1393 glEnable(GL_CULL_FACE);
1395 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1399 gltrackball_rotate (pp->trackball);
1400 mi->polygon_count = 0;
1402 glScalef (16, 16, 16); /* map vp_width/height to the screen */
1404 if (debug_one_gear_p) /* zoom in */
1406 else if (debug_p) /* show the "visible" and "layout" areas */
1408 GLfloat ow = pp->layout_right - pp->render_left;
1409 GLfloat rw = pp->render_right - pp->render_left;
1410 GLfloat s = (pp->vp_width / ow) * 0.85;
1412 glTranslatef (-(ow - rw) / 2, 0, 0);
1417 glScalef (s, s, s); /* zoom in a little more */
1418 glRotatef (-35, 1, 0, 0); /* tilt back */
1419 glRotatef ( 8, 0, 1, 0); /* tilt left */
1420 glTranslatef (0.02, 0.1, 0); /* pan up */
1427 if (!wire_p) glDisable(GL_LIGHTING);
1428 glColor3f (0.6, 0, 0);
1429 glBegin(GL_LINE_LOOP);
1430 glVertex3f (pp->render_left, pp->vp_top, 0);
1431 glVertex3f (pp->render_right, pp->vp_top, 0);
1432 glVertex3f (pp->render_right, pp->vp_bottom, 0);
1433 glVertex3f (pp->render_left, pp->vp_bottom, 0);
1435 glColor3f (0.4, 0, 0);
1437 glVertex3f (pp->vp_left, pp->vp_top, 0);
1438 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
1439 glVertex3f (pp->vp_right, pp->vp_top, 0);
1440 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
1442 glColor3f (0, 0.4, 0);
1443 glBegin(GL_LINE_LOOP);
1444 glVertex3f (pp->layout_left, pp->vp_top, 0);
1445 glVertex3f (pp->layout_right, pp->vp_top, 0);
1446 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1447 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
1449 if (!wire_p) glEnable(GL_LIGHTING);
1452 if (pp->draw_tick++ > 10) /* only do this every N frames */
1455 find_mouse_gear (mi);
1461 glCallList (pp->title_list);
1464 if (mi->fps_p) do_fps (mi);
1467 glXSwapBuffers(dpy, window);
1470 XSCREENSAVER_MODULE ("Pinion", pinion)