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;
60 GLfloat plane_displacement; /* distance between coaxial gears */
62 int debug_size_failures; /* for debugging messages */
63 int debug_position_failures;
64 unsigned long current_length; /* gear count in current train */
65 unsigned long current_blur_length; /* how long have we been blurring? */
67 } pinion_configuration;
70 static pinion_configuration *pps = NULL;
72 /* command line arguments */
73 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
75 static Bool verbose_p = False; /* print progress on stderr */
76 static Bool debug_p = False; /* render as flat schematic */
78 /* internal debugging variables */
79 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
80 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
83 static XrmOptionDescRec opts[] = {
84 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
85 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
86 { "-size", ".gearSize", XrmoptionSepArg, 0 },
87 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
88 { "-debug", ".debug", XrmoptionNoArg, "True" },
89 { "-verbose",".verbose", XrmoptionNoArg, "True" },
92 static argtype vars[] = {
93 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
94 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
95 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
96 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
97 {&debug_p, "debug", "Debug", "False", t_Bool},
98 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
101 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
108 load_fonts (ModeInfo *mi)
110 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
111 pp->font1 = load_texture_font (mi->dpy, "titleFont");
112 pp->font2 = load_texture_font (mi->dpy, "titleFont2");
113 pp->font3 = load_texture_font (mi->dpy, "titleFont3");
118 static void rpm_string (double rpm, char *s);
121 draw_label (ModeInfo *mi)
123 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
128 if (pp->mouse_gear_id)
129 for (i = 0; i < pp->ngears; i++)
130 if (pp->gears[i]->id == pp->mouse_gear_id)
140 sprintf (label, "%d teeth\n", (int) g->nteeth);
141 rpm_string (g->rpm, label + strlen(label));
143 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
145 (g->size == INVOLUTE_SMALL ? "small" :
146 g->size == INVOLUTE_MEDIUM ? "medium"
148 g->tooth_h * MI_HEIGHT(mi));
153 texture_font_data *fd;
154 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
156 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
161 glColor3f (0.8, 0.8, 0);
162 print_texture_label (mi->dpy, fd,
163 mi->xgwa.width, mi->xgwa.height,
173 /* Find the gear in the scene that is farthest to the right or left.
176 farthest_gear (ModeInfo *mi, Bool left_p)
178 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
181 double x = (left_p ? 999999 : -999999);
182 for (i = 0; i < pp->ngears; i++)
184 gear *g = pp->gears[i];
185 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
186 if (left_p ? (x > gx) : (x < gx))
196 /* Compute the revolutions per minute of a gear.
199 compute_rpm (ModeInfo *mi, gear *g)
201 double fps, rpf, rps;
202 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
204 if (fps > 150) fps = 150; /* let's be reasonable... */
205 if (fps < 10) fps = 10;
207 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
208 rps = rpf * fps; /* rotations per second */
212 /* Prints the RPM into a string, doing fancy float formatting.
215 rpm_string (double rpm, char *s)
219 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
220 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
221 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
222 else sprintf (buf, "%.16f",rpm);
225 while (buf[L-1] == '0') buf[--L] = 0;
226 if (buf[L-1] == '.') buf[--L] = 0;
237 /* Create and return a new gear sized for placement next to or on top of
238 the given parent gear (if any.) Returns 0 if out of memory.
241 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
243 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
244 gear *g = (gear *) calloc (1, sizeof (*g));
246 static unsigned long id = 0; /* only used in debugging output */
249 if (coaxial_p && !parent) abort();
252 g->coax_displacement = pp->plane_displacement;
257 if (loop_count > 1000)
258 /* The only time we loop in here is when making a coaxial gear, and
259 trying to pick a radius that is either significantly larger or
260 smaller than its parent. That shouldn't be hard, so something
261 must be really wrong if we can't do that in only a few tries.
265 /* Pick the size of the teeth.
267 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
269 g->tooth_w = parent->tooth_w;
270 g->tooth_h = parent->tooth_h;
271 g->thickness = parent->thickness;
272 g->thickness2 = parent->thickness2;
273 g->thickness3 = parent->thickness3;
275 else /* gears that begin trains get any size they want */
277 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
278 g->tooth_w = 0.007 * scale;
279 g->tooth_h = 0.005 * scale;
280 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
281 g->thickness2 = g->thickness / 4;
282 g->thickness3 = g->thickness;
285 /* Pick the number of teeth, and thus, the radius.
291 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
293 if (g->nteeth < 7 && (random() % 5) != 0)
294 goto AGAIN; /* Let's make very small tooth-counts more rare */
296 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
297 g->r = c / (M_PI * 2); /* c = 2 pi r */
303 if (! coaxial_p) break; /* yes */
304 if (g->nteeth == parent->nteeth) continue; /* ugly */
305 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
306 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
309 /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
311 if (debug_one_gear_p)
312 g->tooth_slope = frand(20)-10;
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 if (pix <= 25) g->size = INVOLUTE_LARGE;
415 else g->size = INVOLUTE_HUGE;
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)];
1164 # ifndef HAVE_JWZGLES
1166 int screen_width = MI_WIDTH (mi);
1167 int screen_height = MI_HEIGHT (mi);
1168 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1172 pp->mouse_gear_id = 0;
1174 /* Poll mouse position */
1179 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1180 &r, &c, &rx, &ry, &x, &y, &m);
1183 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1184 return; /* out of window */
1186 /* Run OpenGL hit detector */
1191 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
1192 glRenderMode (GL_SELECT);
1193 glMatrixMode (GL_PROJECTION);
1196 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
1197 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1198 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
1199 glMatrixMode (GL_MODELVIEW);
1201 draw_gears (mi); /* render into "select" buffer */
1203 glMatrixMode (GL_PROJECTION); /* restore old vp */
1205 glMatrixMode (GL_MODELVIEW);
1207 hits = glRenderMode (GL_RENDER); /* done selecting */
1212 GLuint name_count = 0;
1213 GLuint *p = (GLuint *) selbuf;
1217 for (i = 0; i < hits; i++)
1220 if (*p < min_z) /* find match closest to screen */
1229 if (name_count > 0) /* take first hit */
1230 pp->mouse_gear_id = pnames[0];
1234 #else /* HAVE_JWZGLES */
1235 /* #### not yet implemented */
1236 pp->mouse_gear_id = (pp->ngears > 1 ? pp->gears[1]->id : 0);
1238 #endif /* HAVE_JWZGLES */
1244 /* Window management, etc
1247 reshape_pinion (ModeInfo *mi, int width, int height)
1249 GLfloat h = (GLfloat) height / (GLfloat) width;
1250 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1252 glViewport (0, 0, (GLint) width, (GLint) height);
1254 glMatrixMode(GL_PROJECTION);
1256 gluPerspective (30.0, 1/h, 1.0, 100.0);
1258 glMatrixMode(GL_MODELVIEW);
1260 gluLookAt( 0.0, 0.0, 30.0,
1264 glClear(GL_COLOR_BUFFER_BIT);
1267 GLfloat render_width, layout_width;
1268 pp->vp_height = 1.0;
1271 pp->vp_left = -pp->vp_width/2;
1272 pp->vp_right = pp->vp_width/2;
1273 pp->vp_top = pp->vp_height/2;
1274 pp->vp_bottom = -pp->vp_height/2;
1276 render_width = pp->vp_width * 2;
1277 layout_width = pp->vp_width * 0.8 * gear_size;
1279 pp->render_left = -render_width/2;
1280 pp->render_right = render_width/2;
1282 pp->layout_left = pp->render_right;
1283 pp->layout_right = pp->layout_left + layout_width;
1289 pinion_handle_event (ModeInfo *mi, XEvent *event)
1291 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1293 if (gltrackball_event_handler (event, pp->trackball,
1294 MI_WIDTH (mi), MI_HEIGHT (mi),
1295 &pp->button_down_p))
1297 else if (event->xany.type == KeyPress)
1301 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1302 if (c == ' ' && debug_one_gear_p && pp->ngears)
1304 delete_gear (mi, pp->gears[0]);
1314 init_pinion (ModeInfo *mi)
1316 pinion_configuration *pp;
1318 MI_INIT (mi, pps, NULL);
1320 pp = &pps[MI_SCREEN(mi)];
1322 pp->glx_context = init_GL(mi);
1325 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1326 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1332 pp->plane_displacement = gear_size * 0.1;
1334 pp->trackball = gltrackball_init (False);
1341 draw_pinion (ModeInfo *mi)
1343 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1344 Display *dpy = MI_DISPLAY(mi);
1345 Window window = MI_WINDOW(mi);
1346 Bool wire_p = MI_IS_WIREFRAME(mi);
1348 if (!pp->glx_context)
1351 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1354 glRotatef(current_device_rotation(), 0, 0, 1);
1358 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1359 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1360 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1361 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1363 glEnable(GL_LIGHTING);
1364 glEnable(GL_LIGHT0);
1365 glEnable(GL_DEPTH_TEST);
1366 glEnable(GL_CULL_FACE);
1368 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1369 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1370 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1371 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1374 if (!pp->button_down_p)
1376 if (!debug_one_gear_p || pp->ngears == 0)
1381 glShadeModel(GL_SMOOTH);
1383 glEnable(GL_DEPTH_TEST);
1384 glEnable(GL_NORMALIZE);
1385 glEnable(GL_CULL_FACE);
1387 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1391 gltrackball_rotate (pp->trackball);
1392 mi->polygon_count = 0;
1394 glScalef (16, 16, 16); /* map vp_width/height to the screen */
1396 if (debug_one_gear_p) /* zoom in */
1398 else if (debug_p) /* show the "visible" and "layout" areas */
1400 GLfloat ow = pp->layout_right - pp->render_left;
1401 GLfloat rw = pp->render_right - pp->render_left;
1402 GLfloat s = (pp->vp_width / ow) * 0.85;
1404 glTranslatef (-(ow - rw) / 2, 0, 0);
1409 glScalef (s, s, s); /* zoom in a little more */
1410 glRotatef (-35, 1, 0, 0); /* tilt back */
1411 glRotatef ( 8, 0, 1, 0); /* tilt left */
1412 glTranslatef (0.02, 0.1, 0); /* pan up */
1419 if (!wire_p) glDisable(GL_LIGHTING);
1420 glColor3f (0.6, 0, 0);
1421 glBegin(GL_LINE_LOOP);
1422 glVertex3f (pp->render_left, pp->vp_top, 0);
1423 glVertex3f (pp->render_right, pp->vp_top, 0);
1424 glVertex3f (pp->render_right, pp->vp_bottom, 0);
1425 glVertex3f (pp->render_left, pp->vp_bottom, 0);
1427 glColor3f (0.4, 0, 0);
1429 glVertex3f (pp->vp_left, pp->vp_top, 0);
1430 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
1431 glVertex3f (pp->vp_right, pp->vp_top, 0);
1432 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
1434 glColor3f (0, 0.4, 0);
1435 glBegin(GL_LINE_LOOP);
1436 glVertex3f (pp->layout_left, pp->vp_top, 0);
1437 glVertex3f (pp->layout_right, pp->vp_top, 0);
1438 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1439 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
1441 if (!wire_p) glEnable(GL_LIGHTING);
1444 if (pp->draw_tick++ > 10) /* only do this every N frames */
1447 find_mouse_gear (mi);
1455 if (mi->fps_p) do_fps (mi);
1458 glXSwapBuffers(dpy, window);
1461 XSCREENSAVER_MODULE ("Pinion", pinion)