1 /* pinion, Copyright (c) 2004 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 #include <X11/Intrinsic.h>
14 extern XtAppContext app;
16 #define PROGCLASS "Pinion"
17 #define HACK_INIT init_pinion
18 #define HACK_DRAW draw_pinion
19 #define HACK_RESHAPE reshape_pinion
20 #define HACK_HANDLE_EVENT pinion_handle_event
21 #define EVENT_MASK PointerMotionMask
22 #define sws_opts xlockmore_opts
24 #define DEF_SPIN_SPEED "1.0"
25 #define DEF_SCROLL_SPEED "1.0"
26 #define DEF_GEAR_SIZE "1.0"
27 #define DEF_MAX_RPM "900"
29 #define DEFAULTS "*delay: 15000 \n" \
30 "*showFPS: False \n" \
31 "*wireframe: False \n" \
32 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
33 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
34 "*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
37 #define countof(x) (sizeof((x))/sizeof((*x)))
40 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
42 #include "xlockmore.h"
44 #include "gltrackball.h"
48 #ifdef USE_GL /* whole file */
53 unsigned long id; /* unique name */
54 double x, y, z; /* position */
55 double r; /* radius of the gear, at middle of teeth */
56 double th; /* rotation (degrees) */
58 GLint nteeth; /* how many teeth */
59 double tooth_w, tooth_h; /* size of teeth */
61 double inner_r; /* radius of the (larger) inside hole */
62 double inner_r2; /* radius of the (smaller) inside hole, if any */
63 double inner_r3; /* yet another */
65 double thickness; /* height of the edge */
66 double thickness2; /* height of the (smaller) inside disc if any */
67 double thickness3; /* yet another */
68 int spokes; /* how many spokes inside, if any */
69 int nubs; /* how many little nubbly bits, if any */
70 double spoke_thickness; /* spoke versus hole */
71 GLfloat wobble; /* factory defect! */
72 int motion_blur_p; /* whether it's spinning too fast to draw */
73 int polygons; /* how many polys in this gear */
75 double ratio; /* gearing ratio with previous gears */
76 double rpm; /* approximate revolutions per minute */
78 Bool base_p; /* whether this gear begins a new train */
79 int coax_p; /* whether this is one of a pair of bound gears.
80 1 for first, 2 for second. */
81 double coax_thickness; /* thickness of the other gear in the pair */
82 enum { SMALL, MEDIUM, LARGE } size; /* Controls complexity of mesh. */
91 GLXContext *glx_context;
92 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
93 GLfloat vp_width, vp_height;
94 GLfloat render_left, render_right; /* area in which gears are displayed */
95 GLfloat layout_left, layout_right; /* layout region, on the right side */
101 trackball_state *trackball;
103 unsigned long mouse_gear_id;
105 XFontStruct *xfont1, *xfont2, *xfont3;
106 GLuint font1_dlist, font2_dlist, font3_dlist;
109 } pinion_configuration;
112 static pinion_configuration *pps = NULL;
113 static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
114 static GLfloat plane_displacement = 0.1; /* distance between coaxial gears */
116 static Bool verbose_p = False; /* print progress on stderr */
117 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
118 static Bool debug_p = False; /* render as flat schematic */
119 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
120 static Bool wire_all_p = False; /* in wireframe, do not abbreviate */
122 static int debug_size_failures; /* for debugging messages */
123 static int debug_position_failures;
124 static unsigned long current_length; /* gear count in current train */
125 static unsigned long current_blur_length; /* how long have we been blurring? */
128 static XrmOptionDescRec opts[] = {
129 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
130 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
131 { "-size", ".gearSize", XrmoptionSepArg, 0 },
132 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
133 { "-debug", ".debug", XrmoptionNoArg, "True" },
134 { "-verbose",".verbose", XrmoptionNoArg, "True" },
137 static argtype vars[] = {
138 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
139 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
140 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
141 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
142 {&debug_p, "debug", "Debug", "False", t_Bool},
143 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
146 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
153 load_fonts (ModeInfo *mi)
155 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
156 load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
157 load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
158 load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
163 static void rpm_string (double rpm, char *s);
166 new_label (ModeInfo *mi)
168 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
173 if (pp->mouse_gear_id)
174 for (i = 0; i < pp->ngears; i++)
175 if (pp->gears[i]->id == pp->mouse_gear_id)
185 sprintf (label, "%d teeth\n", g->nteeth);
186 rpm_string (g->rpm, label + strlen(label));
188 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
190 (g->size == SMALL ? "small" : g->size == MEDIUM ? "medium"
192 g->tooth_h * MI_HEIGHT(mi));
195 glNewList (pp->title_list, GL_COMPILE);
200 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
201 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
202 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
203 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
205 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
207 glColor3f (0.8, 0.8, 0);
208 print_gl_string (mi->dpy, f, fl,
209 mi->xgwa.width, mi->xgwa.height,
210 10, mi->xgwa.height - 10,
221 /* Find the gear in the scene that is farthest to the right or left.
224 farthest_gear (ModeInfo *mi, Bool left_p)
226 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
229 double x = (left_p ? 999999 : -999999);
230 for (i = 0; i < pp->ngears; i++)
232 gear *g = pp->gears[i];
233 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
234 if (left_p ? (x > gx) : (x < gx))
244 /* Compute the revolutions per minute of a gear.
247 compute_rpm (ModeInfo *mi, gear *g)
249 double fps, rpf, rps;
250 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
252 if (fps > 150) fps = 150; /* let's be reasonable... */
253 if (fps < 10) fps = 10;
255 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
256 rps = rpf * fps; /* rotations per second */
260 /* Prints the RPM into a string, doing fancy float formatting.
263 rpm_string (double rpm, char *s)
267 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
268 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
269 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
270 else sprintf (buf, "%.16f",rpm);
273 while (buf[L-1] == '0') buf[--L] = 0;
274 if (buf[L-1] == '.') buf[--L] = 0;
281 /* Which of the gear's inside rings is the biggest?
284 biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
286 double r0 = (g->r - g->tooth_h/2);
287 double r1 = g->inner_r;
288 double r2 = g->inner_r2;
289 double r3 = g->inner_r3;
290 double w1 = (r1 ? r0 - r1 : r0);
291 double w2 = (r2 ? r1 - r2 : 0);
292 double w3 = (r3 ? r2 - r3 : 0);
293 double h1 = g->thickness;
294 double h2 = g->thickness2;
295 double h3 = g->thickness3;
297 if (g->spokes) w2 = 0;
299 if (w1 > w2 && w1 > w3)
301 if (posP) *posP = (r0+r1)/2;
302 if (sizeP) *sizeP = w1;
303 if (heightP) *heightP = h1;
306 else if (w2 > w1 && w2 > w3)
308 if (posP) *posP = (r1+r2)/2;
309 if (sizeP) *sizeP = w2;
310 if (heightP) *heightP = h2;
315 if (posP) *posP = (r2+r3)/2;
316 if (sizeP) *sizeP = w3;
317 if (heightP) *heightP = h3;
327 /* Create and return a new gear sized for placement next to or on top of
328 the given parent gear (if any.) Returns 0 if out of memory.
331 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
333 gear *g = (gear *) calloc (1, sizeof (*g));
335 static unsigned long id = 0;
338 if (coaxial_p && !parent) abort();
344 if (loop_count > 1000)
345 /* The only time we loop in here is when making a coaxial gear, and
346 trying to pick a radius that is either significantly larger or
347 smaller than its parent. That shouldn't be hard, so something
348 must be really wrong if we can't do that in only a few tries.
352 /* Pick the size of the teeth.
354 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
356 g->tooth_w = parent->tooth_w;
357 g->tooth_h = parent->tooth_h;
358 g->thickness = parent->thickness;
359 g->thickness2 = parent->thickness2;
360 g->thickness3 = parent->thickness3;
362 else /* gears that begin trains get any size they want */
364 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
365 g->tooth_w = 0.007 * scale;
366 g->tooth_h = 0.005 * scale;
367 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
368 g->thickness2 = g->thickness / 4;
369 g->thickness3 = g->thickness;
372 /* Pick the number of teeth, and thus, the radius.
378 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
380 if (g->nteeth < 7 && (random() % 5) != 0)
381 goto AGAIN; /* Let's make very small tooth-counts more rare */
383 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
384 g->r = c / (M_PI * 2); /* c = 2 pi r */
390 if (! coaxial_p) break; /* yes */
391 if (g->nteeth == parent->nteeth) continue; /* ugly */
392 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
393 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
398 g->color[0] = 0.5 + frand(0.5);
399 g->color[1] = 0.5 + frand(0.5);
400 g->color[2] = 0.5 + frand(0.5);
403 g->color2[0] = g->color[0] * 0.85;
404 g->color2[1] = g->color[1] * 0.85;
405 g->color2[2] = g->color[2] * 0.85;
406 g->color2[3] = g->color[3];
409 /* Decide on shape of gear interior:
410 - just a ring with teeth;
411 - that, plus a thinner in-set "plate" in the middle;
412 - that, plus a thin raised "lip" on the inner plate;
413 - or, a wide lip (really, a thicker third inner plate.)
415 if ((random() % 10) == 0)
417 /* inner_r can go all the way in; there's no inset disc. */
418 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
424 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
425 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
426 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
429 if (g->inner_r2 > (g->r * 0.2))
431 int nn = (random() % 10);
433 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
434 else if (nn <= 7 && g->inner_r2 >= 0.1)
435 g->inner_r3 = g->inner_r2 - 0.01;
439 /* Coaxial gears need to have the same innermost hole size (for the axle.)
440 Use whichever of the two is smaller. (Modifies parent.)
444 double hole1 = (g->inner_r3 ? g->inner_r3 :
445 g->inner_r2 ? g->inner_r2 :
447 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
448 parent->inner_r2 ? parent->inner_r2 :
450 double hole = (hole1 < hole2 ? hole1 : hole2);
451 if (hole <= 0) abort();
453 if (g->inner_r3) g->inner_r3 = hole;
454 else if (g->inner_r2) g->inner_r2 = hole;
455 else g->inner_r = hole;
457 if (parent->inner_r3) parent->inner_r3 = hole;
458 else if (parent->inner_r2) parent->inner_r2 = hole;
459 else parent->inner_r = hole;
462 /* If we have three discs, sometimes make the middle disc be spokes.
464 if (g->inner_r3 && ((random() % 5) == 0))
466 g->spokes = 2 + BELLRAND (5);
467 g->spoke_thickness = 1 + frand(7.0);
468 if (g->spokes == 2 && g->spoke_thickness < 2)
469 g->spoke_thickness += 1;
472 /* Sometimes add little nubbly bits, if there is room.
477 biggest_ring (g, 0, &size, 0);
478 if (size > g->r * 0.2 && (random() % 5) == 0)
480 g->nubs = 1 + (random() % 16);
481 if (g->nubs > 8) g->nubs = 1;
485 if (g->inner_r3 > g->inner_r2) abort();
486 if (g->inner_r2 > g->inner_r) abort();
487 if (g->inner_r > g->r) abort();
489 /* Decide how complex the polygon model should be.
492 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
493 if (pix <= 2.5) g->size = SMALL;
494 else if (pix <= 3.5) g->size = MEDIUM;
495 else g->size = LARGE;
504 /* Given a newly-created gear, place it next to its parent in the scene,
505 with its teeth meshed and the proper velocity. Returns False if it
506 didn't work. (Call this a bunch of times until either it works, or
507 you decide it's probably not going to.)
510 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
512 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
514 /* If this gear takes up more than 1/3rd of the screen, it's no good.
516 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
517 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
519 if (verbose_p && debug_placement_p && 0)
521 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
523 (g->r + g->tooth_h), gear_size,
524 pp->vp_width, pp->vp_height);
525 debug_size_failures++;
529 /* Compute this gear's velocity.
533 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
534 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
538 g->ratio = parent->ratio; /* bound gears have the same ratio */
540 g->rpm = parent->rpm;
541 g->wobble = parent->wobble;
545 /* Gearing ratio is the ratio of the number of teeth to previous gear
546 (which is also the ratio of the circumferences.)
548 g->ratio = (double) parent->nteeth / (double) g->nteeth;
550 /* Set our initial rotation to match that of the previous gear,
551 multiplied by the gearing ratio. (This is finessed later,
552 once we know the exact position of the gear relative to its
555 g->th = -(parent->th * g->ratio);
557 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
559 double off = (180.0 / g->nteeth);
566 /* ratios are cumulative for all gears in the train. */
567 g->ratio *= parent->ratio;
571 /* Place the gear relative to the parent.
576 gear *rg = farthest_gear (mi, False);
577 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
578 if (right < pp->layout_left) /* place off screen */
579 right = pp->layout_left;
581 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
585 if (debug_one_gear_p)
590 double off = plane_displacement;
594 g->z = parent->z + (g->r > parent->r /* small gear on top */
597 if (parent->r > g->r) /* mark which is top and which is bottom */
601 parent->wobble = 0; /* looks bad when axle moves */
610 g->coax_thickness = parent->thickness;
611 parent->coax_thickness = g->thickness;
613 /* Don't let the train get too close to or far from the screen.
614 If we're getting too close, give up on this gear.
615 (But getting very far away is fine.)
617 if (g->z >= off * 4 ||
620 if (verbose_p && debug_placement_p)
621 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
623 debug_position_failures++;
627 else /* position it somewhere next to the parent. */
629 double r_off = parent->r + g->r;
632 if ((random() % 3) != 0)
633 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
635 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
637 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
638 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
641 /* If the angle we picked would have positioned this gear
642 more than halfway off screen, that's no good. */
643 if (g->y > pp->vp_top ||
644 g->y < pp->vp_bottom)
646 if (verbose_p && debug_placement_p)
647 fprintf (stderr, "%s: placement: out of bounds: %s\n",
648 progname, (g->y > pp->vp_top ? "top" : "bottom"));
649 debug_position_failures++;
653 /* avoid accidentally changing sign of "th" in the math below. */
654 g->th += (g->th > 0 ? 360 : -360);
656 /* Adjust the rotation of the gear so that its teeth line up with its
657 parent, based on the position of the gear and the current rotation
661 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
662 double g_c = 2 * M_PI * g->r; /* circumference of g */
664 double p_t = p_c * (angle/360.0); /* distance travelled along
665 circumference of parent when
666 moving "angle" degrees along
668 double g_rat = p_t / g_c; /* if travelling that distance
669 along circumference of g,
670 ratio of g's circumference
672 double g_th = 360.0 * g_rat; /* that ratio in degrees */
674 g->th += angle + g_th;
678 if (debug_one_gear_p)
684 /* If the position we picked for this gear would cause it to already
685 be visible on the screen, give up. This can happen when the train
686 is growing backwards, and we don't want to see gears flash into
689 if (g->x - g->r - g->tooth_h < pp->render_right)
691 if (verbose_p && debug_placement_p)
692 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
693 debug_position_failures++;
697 /* If the position we picked for this gear causes it to overlap
698 with any earlier gear in the train, give up.
703 for (i = pp->ngears-1; i >= 0; i--)
705 gear *og = pp->gears[i];
707 if (og == g) continue;
708 if (og == parent) continue;
709 if (g->z != og->z) continue; /* Ignore unless on same layer */
711 /* Collision detection without sqrt:
712 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
713 d < r1 + r2 d^2 < (r1 + r2)^2
715 if (((g->x - og->x) * (g->x - og->x) +
716 (g->y - og->y) * (g->y - og->y)) <
717 ((g->r + g->tooth_h + og->r + og->tooth_h) *
718 (g->r + g->tooth_h + og->r + og->tooth_h)))
720 if (verbose_p && debug_placement_p)
721 fprintf (stderr, "%s: placement: collision with %lu\n",
723 debug_position_failures++;
732 /* Make deeper gears be darker.
735 double depth = g->z / plane_displacement;
736 double brightness = 1 + (depth / 6);
738 if (brightness < limit) brightness = limit;
739 if (brightness > 1/limit) brightness = 1/limit;
740 g->color[0] *= brightness;
741 g->color[1] *= brightness;
742 g->color[2] *= brightness;
743 g->color2[0] *= brightness;
744 g->color2[1] *= brightness;
745 g->color2[2] *= brightness;
748 /* If a single frame of animation would cause the gear to rotate by
749 more than 1/2 the size of a single tooth, then it won't look right:
750 the gear will appear to be turning at some lower harmonic of its
754 double ratio = g->ratio * spin_speed;
755 double blur_limit = 180.0 / g->nteeth;
757 if (ratio > blur_limit)
758 g->motion_blur_p = 1;
762 /* ride until the wheels fall off... */
763 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
764 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
765 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
766 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
767 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
768 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
779 glDeleteLists (g->dlist, 1);
784 /* Make a new gear, place it next to its parent in the scene,
785 with its teeth meshed and the proper velocity. Returns the gear;
786 or 0 if it didn't work. (Call this a bunch of times until either
787 it works, or you decide it's probably not going to.)
790 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
792 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
799 if (loop_count >= 100)
807 g = new_gear (mi, parent, coaxial_p);
808 if (!g) return 0; /* out of memory? */
810 if (place_gear (mi, g, parent, coaxial_p))
816 /* We got a gear, and it is properly positioned.
817 Insert it in the scene.
819 if (pp->ngears + 2 >= pp->gears_size)
821 pp->gears_size += 100;
822 pp->gears = (gear **) realloc (pp->gears,
823 pp->gears_size * sizeof (*pp->gears));
826 fprintf (stderr, "%s: out of memory (%d gears)\n",
827 progname, pp->gears_size);
831 pp->gears[pp->ngears++] = g;
836 static void delete_gear (ModeInfo *mi, gear *g);
839 push_gear (ModeInfo *mi)
841 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
843 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
845 Bool tried_coaxial_p = False;
846 Bool coaxial_p = False;
847 Bool last_ditch_coax_p = False;
850 debug_size_failures = 0;
851 debug_position_failures = 0;
855 if (loop_count > 100) abort(); /* we're doomed! */
859 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
860 reset things to a sane velocity.
862 10,000 RPM at 30 FPS = 5.5 rotations per frame.
863 1,000 RPM at 30 FPS = 0.5 rotations per frame.
864 600 RPM at 30 FPS = 3 frames per rotation.
865 200 RPM at 30 FPS = 9 frames per rotation.
866 100 RPM at 30 FPS = 18 frames per rotation.
867 50 RPM at 30 FPS = 36 frames per rotation.
868 10 RPM at 30 FPS = 3 sec per rotation.
869 1 RPM at 30 FPS = 30 sec per rotation.
870 .5 RPM at 30 FPS = 1 min per rotation.
871 .1 RPM at 30 FPS = 5 min per rotation.
873 if (parent && parent->rpm > max_rpm)
878 rpm_string (parent->rpm, buf);
879 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
884 /* If the last N gears we've placed have all been motion-blurred, then
885 it's a safe guess that we've wandered off into the woods and aren't
886 coming back. Bail on this train.
888 if (current_blur_length >= 10)
891 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
897 /* Sometimes, try to make a coaxial gear.
899 if (parent && !parent->coax_p && (random() % 40) == 0)
901 tried_coaxial_p = True;
903 g = place_new_gear (mi, parent, coaxial_p);
906 /* Try to make a regular gear.
911 g = place_new_gear (mi, parent, coaxial_p);
914 /* If we couldn't make a regular gear, then try to make a coxial gear
915 (unless we already tried that.)
917 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
919 tried_coaxial_p = True;
921 g = place_new_gear (mi, parent, coaxial_p);
923 last_ditch_coax_p = True;
926 /* If we couldn't do that either, then the train has hit a dead end:
933 fprintf (stderr, "%s: dead end!\n\n", progname);
935 g = place_new_gear (mi, parent, coaxial_p);
940 /* Unable to make/place any gears at all!
941 This can happen if we've backed ourself into a corner very near
942 the top-right or bottom-right corner of the growth zone.
943 It's time to add a gear, but there's no room to add one!
944 In that case, let's just wipe all the gears that are in the
945 growth zone and try again.
949 if (verbose_p && debug_placement_p)
951 "%s: placement: resetting growth zone! "
952 "failed: %d size, %d pos\n",
954 debug_size_failures, debug_position_failures);
955 for (i = pp->ngears-1; i >= 0; i--)
957 gear *g = pp->gears[i];
958 if (g->x - g->r - g->tooth_h < pp->render_left)
966 if (g->x != parent->x) abort();
967 if (g->y != parent->y) abort();
968 if (g->z == parent->z) abort();
969 if (g->r == parent->r) abort();
970 if (g->th != parent->th) abort();
971 if (g->ratio != parent->ratio) abort();
972 if (g->rpm != parent->rpm) abort();
977 fprintf (stderr, "%s: %5lu ", progname, g->id);
979 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
980 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
981 g->coax_p ? '1' : ' '),
984 fprintf (stderr, " %2d%%",
985 (int) (g->r * 2 * 100 / pp->vp_height));
986 fprintf (stderr, " %2d teeth", g->nteeth);
987 fprintf (stderr, " %3.0f rpm;", g->rpm);
990 char buf1[50], buf2[50], buf3[100];
991 *buf1 = 0; *buf2 = 0; *buf3 = 0;
992 if (debug_size_failures)
993 sprintf (buf1, "%3d sz", debug_size_failures);
994 if (debug_position_failures)
995 sprintf (buf2, "%2d pos", debug_position_failures);
997 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
998 fprintf (stderr, "%-21s", buf3);
1001 if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
1002 fprintf (stderr, "\n");
1010 if (g->motion_blur_p)
1011 current_blur_length++;
1013 current_blur_length = 0;
1018 /* Remove the given gear from the scene and free it.
1021 delete_gear (ModeInfo *mi, gear *g)
1023 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1026 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
1027 if (pp->gears[i] == g) break;
1028 if (pp->gears[i] != g) abort();
1030 for (; i < pp->ngears-1; i++) /* pull later entries forward */
1031 pp->gears[i] = pp->gears[i+1];
1038 /* Update the position of each gear in the scene.
1041 scroll_gears (ModeInfo *mi)
1043 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1046 for (i = 0; i < pp->ngears; i++)
1047 pp->gears[i]->x -= (scroll_speed * 0.002);
1049 /* if the right edge of any gear is off screen to the left, delete it.
1051 for (i = pp->ngears-1; i >= 0; i--)
1053 gear *g = pp->gears[i];
1054 if (g->x + g->r + g->tooth_h < pp->render_left)
1055 delete_gear (mi, g);
1058 /* if the right edge of the last-added gear is left of the right edge
1059 of the layout area, add another gear.
1064 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1065 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1070 if (debug_one_gear_p) break;
1074 if (i > 1 && verbose_p)
1075 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1080 /* Update the rotation of each gear in the scene.
1083 spin_gears (ModeInfo *mi)
1085 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1088 for (i = 0; i < pp->ngears; i++)
1090 gear *g = pp->gears[i];
1091 double off = (g->ratio * spin_speed);
1101 /* Run the animation fast (without displaying anything) until the first
1102 gear is just about to come on screen. This is to avoid a big delay
1103 with a blank screen when -scroll is low.
1108 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1109 if (debug_one_gear_p) return;
1112 gear *g = farthest_gear (mi, True);
1113 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1121 /* Rendering the 3D objects into the scene.
1125 /* Draws an uncapped tube of the given radius extending from top to bottom,
1126 with faces pointing either in or out.
1129 draw_ring (ModeInfo *mi, int segments,
1130 GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1134 Bool wire_p = MI_IS_WIREFRAME(mi);
1135 GLfloat width = M_PI * 2 / segments;
1139 glFrontFace (in_p ? GL_CCW : GL_CW);
1140 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1141 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1143 GLfloat th = i * width;
1144 GLfloat cth = cos(th);
1145 GLfloat sth = sin(th);
1147 glNormal3f (-cth, -sth, 0);
1149 glNormal3f (cth, sth, 0);
1150 glVertex3f (cth * r, sth * r, top);
1151 glVertex3f (cth * r, sth * r, bottom);
1159 glBegin (GL_LINE_LOOP);
1160 for (i = 0; i < segments; i++)
1162 GLfloat th = i * width;
1163 glVertex3f (cos(th) * r, sin(th) * r, top);
1166 glBegin (GL_LINE_LOOP);
1167 for (i = 0; i < segments; i++)
1169 GLfloat th = i * width;
1170 glVertex3f (cos(th) * r, sin(th) * r, bottom);
1179 /* Draws a donut-shaped disc between the given radii,
1180 with faces pointing either up or down.
1181 The first radius may be 0, in which case, a filled disc is drawn.
1184 draw_disc (ModeInfo *mi, int segments,
1185 GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1189 Bool wire_p = MI_IS_WIREFRAME(mi);
1190 GLfloat width = M_PI * 2 / segments;
1192 if (ra < 0) abort();
1193 if (rb <= 0) abort();
1196 glFrontFace (up_p ? GL_CW : GL_CCW);
1198 glFrontFace (up_p ? GL_CCW : GL_CW);
1201 glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1203 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1205 glNormal3f (0, 0, (up_p ? -1 : 1));
1207 if (ra == 0 && !wire_p)
1208 glVertex3f (0, 0, z);
1210 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1212 GLfloat th = i * width;
1213 GLfloat cth = cos(th);
1214 GLfloat sth = sin(th);
1215 if (wire_p || ra != 0)
1216 glVertex3f (cth * ra, sth * ra, z);
1217 glVertex3f (cth * rb, sth * rb, z);
1225 /* Draws N thick radial lines between the given radii,
1226 with faces pointing either up or down.
1229 draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1230 GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1234 Bool wire_p = MI_IS_WIREFRAME(mi);
1237 int insegs, outsegs;
1241 if (ra <= 0 || rb <= 0) abort();
1245 while (segments2 < segments) /* need a multiple of N >= segments */
1246 segments2 += n; /* (yes, this is a moronic way to find that) */
1248 insegs = ((float) (segments2 / n) + 0.5) / thickness;
1249 outsegs = (segments2 / n) - insegs;
1250 if (insegs <= 0) insegs = 1;
1251 if (outsegs <= 0) outsegs = 1;
1253 segments2 = (insegs + outsegs) * n;
1254 width = M_PI * 2 / segments2;
1258 for (i = 0; i < segments2; i++, tick++)
1260 GLfloat th1 = i * width;
1261 GLfloat th2 = th1 + width;
1262 GLfloat cth1 = cos(th1);
1263 GLfloat sth1 = sin(th1);
1264 GLfloat cth2 = cos(th2);
1265 GLfloat sth2 = sin(th2);
1268 int changed = (i == 0);
1270 if (state == 0 && tick == insegs)
1271 tick = 0, state = 1, changed = 1;
1272 else if (state == 1 && tick == outsegs)
1273 tick = 0, state = 0, changed = 1;
1275 if ((state == 1 || /* in */
1276 (state == 0 && changed)) &&
1277 (!wire_p || wire_all_p))
1280 glFrontFace (GL_CCW);
1281 glBegin (wire_p ? GL_LINES : GL_QUADS);
1282 glNormal3f (0, 0, -1);
1283 glVertex3f (cth1 * ra, sth1 * ra, z1);
1284 glVertex3f (cth1 * rb, sth1 * rb, z1);
1285 glVertex3f (cth2 * rb, sth2 * rb, z1);
1286 glVertex3f (cth2 * ra, sth2 * ra, z1);
1291 glFrontFace (GL_CW);
1292 glBegin (wire_p ? GL_LINES : GL_QUADS);
1293 glNormal3f (0, 0, 1);
1294 glVertex3f (cth1 * ra, sth1 * ra, z2);
1295 glVertex3f (cth1 * rb, sth1 * rb, z2);
1296 glVertex3f (cth2 * rb, sth2 * rb, z2);
1297 glVertex3f (cth2 * ra, sth2 * ra, z2);
1302 if (state == 1 && changed) /* entering "in" state */
1305 glFrontFace (GL_CW);
1306 glBegin (wire_p ? GL_LINES : GL_QUADS);
1307 do_normal (cth1 * rb, sth1 * rb, z1,
1308 cth1 * ra, sth1 * ra, z1,
1309 cth1 * rb, sth1 * rb, z2);
1310 glVertex3f (cth1 * ra, sth1 * ra, z1);
1311 glVertex3f (cth1 * rb, sth1 * rb, z1);
1312 glVertex3f (cth1 * rb, sth1 * rb, z2);
1313 glVertex3f (cth1 * ra, sth1 * ra, z2);
1318 if (state == 0 && changed) /* entering "out" state */
1321 glFrontFace (GL_CCW);
1322 glBegin (wire_p ? GL_LINES : GL_QUADS);
1323 do_normal (cth2 * ra, sth2 * ra, z1,
1324 cth2 * rb, sth2 * rb, z1,
1325 cth2 * rb, sth2 * rb, z2);
1326 glVertex3f (cth2 * ra, sth2 * ra, z1);
1327 glVertex3f (cth2 * rb, sth2 * rb, z1);
1328 glVertex3f (cth2 * rb, sth2 * rb, z2);
1329 glVertex3f (cth2 * ra, sth2 * ra, z2);
1341 /* Draws some bumps (embedded cylinders) on the gear.
1344 draw_gear_nubs (ModeInfo *mi, gear *g)
1346 Bool wire_p = MI_IS_WIREFRAME(mi);
1349 int steps = (g->size != LARGE ? 5 : 20);
1350 double r, size, height;
1355 if (! g->nubs) return 0;
1357 which = biggest_ring (g, &r, &size, &height);
1361 cc = (which == 1 ? g->color : g->color2);
1362 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1364 width = M_PI * 2 / g->nubs;
1365 off = M_PI / (g->nteeth * 2); /* align first nub with a tooth */
1367 for (i = 0; i < g->nubs; i++)
1369 GLfloat th = (i * width) + off;
1371 glTranslatef (cos(th) * r, sin(th) * r, 0);
1373 if (wire_p && !wire_all_p)
1374 polys += draw_ring (mi, (g->size == LARGE ? steps/2 : steps),
1378 polys += draw_disc (mi, steps, 0, size, -height, True);
1379 polys += draw_disc (mi, steps, 0, size, height, False);
1380 polys += draw_ring (mi, steps, size, -height, height, False);
1389 /* Draws a much simpler representation of a gear.
1392 draw_gear_schematic (ModeInfo *mi, gear *g)
1394 Bool wire_p = MI_IS_WIREFRAME(mi);
1397 GLfloat width = M_PI * 2 / g->nteeth;
1399 if (!wire_p) glDisable(GL_LIGHTING);
1400 glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1403 for (i = 0; i < g->nteeth; i++)
1405 GLfloat th = (i * width) + (width/4);
1406 glVertex3f (0, 0, -g->thickness/2);
1407 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1412 glBegin (GL_LINE_LOOP);
1413 for (i = 0; i < g->nteeth; i++)
1415 GLfloat th = (i * width) + (width/4);
1416 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1421 if (!wire_p) glEnable(GL_LIGHTING);
1426 /* Renders all the interior (non-toothy) parts of a gear:
1427 the discs, axles, etc.
1430 draw_gear_interior (ModeInfo *mi, gear *g)
1432 Bool wire_p = MI_IS_WIREFRAME(mi);
1435 int steps = g->nteeth * 2;
1436 if (steps < 10) steps = 10;
1437 if ((wire_p && !wire_all_p) || g->size != LARGE) steps /= 2;
1438 if (g->size != LARGE && steps > 16) steps = 16;
1440 /* ring 1 (facing in) is done in draw_gear_teeth */
1442 /* ring 2 (facing in) and disc 2
1446 GLfloat ra = g->inner_r * 1.04; /* slightly larger than inner_r */
1447 GLfloat rb = g->inner_r2; /* since the points don't line up */
1448 GLfloat za = -g->thickness2/2;
1449 GLfloat zb = g->thickness2/2;
1451 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1453 if ((g->coax_p != 1 && !g->inner_r3) ||
1454 (wire_p && wire_all_p))
1455 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1457 if (wire_p && wire_all_p)
1458 polys += draw_ring (mi, steps, ra, za, zb, True); /* ring facing in */
1461 polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1462 steps, ra, rb, za, zb);
1463 else if (!wire_p || wire_all_p)
1465 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1466 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1470 /* ring 3 (facing in and out) and disc 3
1474 GLfloat ra = g->inner_r2;
1475 GLfloat rb = g->inner_r3;
1476 GLfloat za = -g->thickness3/2;
1477 GLfloat zb = g->thickness3/2;
1479 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1481 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1483 if (g->coax_p != 1 || (wire_p && wire_all_p))
1484 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1486 if (!wire_p || wire_all_p)
1488 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1489 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1497 GLfloat cap_height = g->coax_thickness/3;
1499 GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1500 g->inner_r2 ? g->inner_r2 :
1502 GLfloat za = -(g->thickness/2 + cap_height);
1503 GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
1505 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1507 if (wire_p && !wire_all_p) steps /= 2;
1509 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1511 if (!wire_p || wire_all_p)
1513 polys += draw_disc (mi, steps, 0, ra, za, True); /* top plate */
1514 polys += draw_disc (mi, steps, 0, ra, zb, False); /* bottom plate */
1521 /* gear_teeth_geometry computes the vertices and normals of the teeth
1522 of a gear. This is the heavy lifting: there are a ton of polygons
1523 around the perimiter of a gear, and the normals are difficult (not
1524 radial or right angles.)
1526 It would be nice if we could cache this, but the numbers are
1527 different for essentially every gear:
1529 - Every gear has a different inner_r, so the vertices of the
1530 inner ring (and thus, the triangle fans on the top and bottom
1531 faces) are different in a non-scalable way.
1533 - If the ratio between tooth_w and tooth_h changes, the normals
1534 on the outside edges of the teeth are invalid (this can happen
1535 every time we start a new train.)
1537 So instead, we rely on OpenGL display lists to do the cacheing for
1538 us -- we only compute all these normals once per gear, instead of
1539 once per gear per frame.
1545 XYZ *fnormals; /* face normals */
1546 XYZ *pnormals; /* point normals */
1551 tooth_normals (tooth_face *f)
1555 /* Compute the face normals for each facet. */
1556 for (i = 0; i < f->npoints; i++)
1560 int b = (i == f->npoints-1 ? 0 : i+1);
1565 f->fnormals[i] = calc_normal (p1, p2, p3);
1568 /* From the face normals, compute the vertex normals
1569 (by averaging the normals of adjascent faces.)
1571 for (i = 0; i < f->npoints; i++)
1573 int a = (i == 0 ? f->npoints-1 : i-1);
1575 XYZ n1 = f->fnormals[a]; /* normal of [i-1 - i] face */
1576 XYZ n2 = f->fnormals[b]; /* normal of [i - i+1] face */
1577 f->pnormals[i].x = (n1.x + n2.x) / 2;
1578 f->pnormals[i].y = (n1.y + n2.y) / 2;
1579 f->pnormals[i].z = (n1.z + n2.z) / 2;
1585 gear_teeth_geometry (ModeInfo *mi, gear *g,
1586 tooth_face *orim, /* outer rim (the teeth) */
1587 tooth_face *irim) /* inner rim (the hole) */
1590 int ppt = 9; /* max points per tooth */
1591 GLfloat width = M_PI * 2 / g->nteeth;
1592 GLfloat rh = g->tooth_h;
1595 /* Approximate shape of an "involute" gear tooth.
1598 th0 th1 th2 th3 th4 th5 th6 th7 th8 th9 th10
1599 : : : : : : : : : : :
1600 : : : : : : : : : : :
1601 r0 ........:..:..:...___________...:..:..:......:......:..
1602 : : : /: : :\ : : : : :
1603 : : : / : : : \ : : : : :
1604 : : :/ : : : \: : : : :
1605 r1 ........:.....@...:....:....:...@..:..:......:......:..
1606 : : @: : : : :@ : : : :
1607 (R) ...........:...@.:...:....:....:...:.@..........:......:......
1608 : :@ : : : : : @: : : :
1609 r2 ........:..@..:...:....:....:...:..@:........:......:..
1610 : /: : : : : : :\ : : :
1611 :/ : : : : : : : \: : : /
1612 r3 ......__/..:..:...:....:....:...:..:..\______________/
1613 : : : : : : : : : : :
1614 | : : : : : : : | : :
1615 : : : : : : : : : : :
1616 | : : : : : : : | : :
1617 r4 ......__:_____________________________:________________
1624 r[0] = R + (rh * 0.5);
1625 r[1] = R + (rh * 0.25);
1626 r[2] = R - (r[1]-R);
1627 r[3] = R - (r[0]-R);
1630 th[0] = -tw * (g->size == SMALL ? 0.5 : g->size == MEDIUM ? 0.41 : 0.45);
1632 th[2] = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
1633 th[3] = -tw * (g->size == MEDIUM ? 0.1 : 0.04);
1640 th[10]= th[0] + width;
1643 orim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
1644 orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
1645 orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
1648 irim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
1649 irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
1650 irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
1652 if (!orim->points || !orim->pnormals || !orim->fnormals ||
1653 !irim->points || !irim->pnormals || !irim->fnormals)
1655 fprintf (stderr, "%s: out of memory\n", progname);
1659 /* First, compute the coordinates of every point used for every tooth.
1661 for (i = 0; i < g->nteeth; i++)
1663 GLfloat TH = (i * width) + (width/4);
1666 # define PUSH(OPR,IPR,PTH) \
1667 orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
1668 orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
1670 irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
1671 irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
1674 if (g->size == SMALL)
1676 PUSH(3, 4, 0); /* tooth left 1 */
1677 PUSH(0, 4, 4); /* tooth top middle */
1679 else if (g->size == MEDIUM)
1681 PUSH(3, 4, 0); /* tooth left 1 */
1682 PUSH(0, 4, 3); /* tooth top left */
1683 PUSH(0, 4, 5); /* tooth top right */
1684 PUSH(3, 4, 8); /* tooth right 3 */
1686 else if (g->size == LARGE)
1688 PUSH(3, 4, 0); /* tooth left 1 */
1689 PUSH(2, 4, 1); /* tooth left 2 */
1690 PUSH(1, 4, 2); /* tooth left 3 */
1691 PUSH(0, 4, 3); /* tooth top left */
1692 PUSH(0, 4, 5); /* tooth top right */
1693 PUSH(1, 4, 6); /* tooth right 1 */
1694 PUSH(2, 4, 7); /* tooth right 2 */
1695 PUSH(3, 4, 8); /* tooth right 3 */
1696 PUSH(3, 4, 9); /* gap top */
1702 if (i == 0 && orim->npoints > ppt) abort(); /* go update "ppt"! */
1705 tooth_normals (orim);
1706 tooth_normals (irim);
1710 /* Renders all teeth of a gear.
1713 draw_gear_teeth (ModeInfo *mi, gear *g)
1715 Bool wire_p = MI_IS_WIREFRAME(mi);
1716 Bool show_normals_p = False;
1720 GLfloat z1 = -g->thickness/2;
1721 GLfloat z2 = g->thickness/2;
1723 tooth_face orim, irim;
1724 gear_teeth_geometry (mi, g, &orim, &irim);
1726 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1728 /* Draw the outer rim (the teeth)
1729 (In wire mode, this draws just the upright lines.)
1731 glFrontFace (GL_CW);
1732 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1733 for (i = 0; i < orim.npoints; i++)
1735 glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
1736 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1737 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1739 /* Show the face normal vectors */
1740 if (wire_p && show_normals_p)
1742 XYZ n = orim.fnormals[i];
1744 int b = (i == orim.npoints-1 ? 0 : i+1);
1745 GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
1746 GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
1747 GLfloat z = (z1 + z2) / 2;
1748 glVertex3f (x, y, z);
1749 glVertex3f (x + n.x, y + n.y, z);
1752 /* Show the vertex normal vectors */
1753 if (wire_p && show_normals_p)
1755 XYZ n = orim.pnormals[i];
1756 GLfloat x = orim.points[i].x;
1757 GLfloat y = orim.points[i].y;
1758 GLfloat z = (z1 + z2) / 2;
1759 glVertex3f (x, y, z);
1760 glVertex3f (x + n.x, y + n.y, z);
1764 if (!wire_p) /* close the quad loop */
1766 glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
1767 glVertex3f (orim.points[0].x, orim.points[0].y, z1);
1768 glVertex3f (orim.points[0].x, orim.points[0].y, z2);
1770 polys += orim.npoints;
1773 /* Draw the outer rim circles, in wire mode */
1776 glBegin (GL_LINE_LOOP);
1777 for (i = 0; i < orim.npoints; i++)
1778 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1780 glBegin (GL_LINE_LOOP);
1781 for (i = 0; i < orim.npoints; i++)
1782 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1787 /* Draw the inner rim (the hole)
1788 (In wire mode, this draws just the upright lines.)
1790 glFrontFace (GL_CCW);
1791 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1792 for (i = 0; i < irim.npoints; i++)
1794 glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
1795 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1796 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1798 /* Show the face normal vectors */
1799 if (wire_p && show_normals_p)
1801 XYZ n = irim.fnormals[i];
1803 int b = (i == irim.npoints-1 ? 0 : i+1);
1804 GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
1805 GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
1806 GLfloat z = (z1 + z2) / 2;
1807 glVertex3f (x, y, z);
1808 glVertex3f (x - n.x, y - n.y, z);
1811 /* Show the vertex normal vectors */
1812 if (wire_p && show_normals_p)
1814 XYZ n = irim.pnormals[i];
1815 GLfloat x = irim.points[i].x;
1816 GLfloat y = irim.points[i].y;
1817 GLfloat z = (z1 + z2) / 2;
1818 glVertex3f (x, y, z);
1819 glVertex3f (x - n.x, y - n.y, z);
1823 if (!wire_p) /* close the quad loop */
1825 glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
1826 glVertex3f (irim.points[0].x, irim.points[0].y, z1);
1827 glVertex3f (irim.points[0].x, irim.points[0].y, z2);
1829 polys += irim.npoints;
1832 /* Draw the inner rim circles, in wire mode
1836 glBegin (GL_LINE_LOOP);
1837 for (i = 0; i < irim.npoints; i++)
1838 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1840 glBegin (GL_LINE_LOOP);
1841 for (i = 0; i < irim.npoints; i++)
1842 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1847 /* Draw the side (the flat bit)
1849 if (!wire_p || wire_all_p)
1852 if (irim.npoints != orim.npoints) abort();
1853 for (z = z1; z <= z2; z += z2-z1)
1855 glFrontFace (z == z1 ? GL_CCW : GL_CW);
1856 glNormal3f (0, 0, z);
1857 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1858 for (i = 0; i < orim.npoints; i++)
1860 glVertex3f (orim.points[i].x, orim.points[i].y, z);
1861 glVertex3f (irim.points[i].x, irim.points[i].y, z);
1863 if (!wire_p) /* close the quad loop */
1865 glVertex3f (orim.points[0].x, orim.points[0].y, z);
1866 glVertex3f (irim.points[0].x, irim.points[0].y, z);
1868 polys += orim.npoints;
1874 free (irim.fnormals);
1875 free (irim.pnormals);
1878 free (orim.fnormals);
1879 free (orim.pnormals);
1885 /* Render one gear, unrotated at 0,0.
1888 draw_gear_1 (ModeInfo *mi, gear *g)
1890 Bool wire_p = MI_IS_WIREFRAME(mi);
1893 static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
1894 static GLfloat shiny = 128.0;
1896 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
1897 glMateriali (GL_FRONT, GL_SHININESS, shiny);
1898 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1899 glColor3f (g->color[0], g->color[1], g->color[2]);
1901 if (debug_p && wire_p)
1902 polys += draw_gear_schematic (mi, g);
1906 glRotatef (g->wobble, 1, 0, 0);
1907 polys += draw_gear_teeth (mi, g);
1908 polys += draw_gear_interior (mi, g);
1909 polys += draw_gear_nubs (mi, g);
1916 /* Render one gear in the proper position, creating the gear's
1917 display list first if necessary.
1920 draw_gear (ModeInfo *mi, int which)
1922 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1923 gear *g = pp->gears[which];
1926 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1927 g->x - g->r - g->tooth_h <= pp->render_right);
1929 if (!visible_p && !debug_p)
1934 g->dlist = glGenLists (1);
1937 /* I don't know how many display lists a GL implementation
1938 is supposed to provide, but hopefully it's more than
1939 "a few hundred", or we'll be in trouble...
1941 check_gl_error ("glGenLists");
1945 glNewList (g->dlist, GL_COMPILE);
1946 g->polygons = draw_gear_1 (mi, g);
1952 glTranslatef (g->x, g->y, g->z);
1954 if (g->motion_blur_p && !pp->button_down_p)
1956 /* If we're in motion-blur mode, then draw the gear so that each
1957 frame rotates it by exactly one half tooth-width, so that it
1958 looks flickery around the edges. But, revert to the normal
1959 way when the mouse button is down lest the user see overlapping
1962 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1968 glRotatef (th, 0, 0, 1);
1973 mi->polygon_count += draw_gear_schematic (mi, g);
1976 glCallList (g->dlist);
1977 mi->polygon_count += g->polygons;
1985 /* Render all gears.
1988 draw_gears (ModeInfo *mi)
1990 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1991 Bool wire_p = MI_IS_WIREFRAME(mi);
1994 glColor4f (1, 1, 0.8, 1);
1998 for (i = 0; i < pp->ngears; i++)
2001 /* draw a line connecting gears that are, uh, geared. */
2004 static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2006 GLfloat ox=0, oy=0, oz=0;
2008 if (!wire_p) glDisable(GL_LIGHTING);
2009 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2010 glColor3f (color[0], color[1], color[2]);
2012 for (i = 0; i < pp->ngears; i++)
2014 gear *g = pp->gears[i];
2015 glBegin(GL_LINE_STRIP);
2016 glVertex3f (g->x, g->y, g->z - off);
2017 glVertex3f (g->x, g->y, g->z + off);
2018 if (i > 0 && !g->base_p)
2019 glVertex3f (ox, oy, oz + off);
2025 if (!wire_p) glEnable(GL_LIGHTING);
2030 /* Mouse hit detection
2033 find_mouse_gear (ModeInfo *mi)
2035 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2036 int screen_width = MI_WIDTH (mi);
2037 int screen_height = MI_HEIGHT (mi);
2038 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2042 pp->mouse_gear_id = 0;
2044 /* Poll mouse position */
2049 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2050 &r, &c, &rx, &ry, &x, &y, &m);
2053 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2054 return; /* out of window */
2056 /* Run OpenGL hit detector */
2061 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
2062 glRenderMode (GL_SELECT);
2063 glMatrixMode (GL_PROJECTION);
2066 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
2067 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2068 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
2069 glMatrixMode (GL_MODELVIEW);
2071 draw_gears (mi); /* render into "select" buffer */
2073 glMatrixMode (GL_PROJECTION); /* restore old vp */
2075 glMatrixMode (GL_MODELVIEW);
2077 hits = glRenderMode (GL_RENDER); /* done selecting */
2082 GLuint name_count = 0;
2083 GLuint *p = (GLuint *) selbuf;
2087 for (i = 0; i < hits; i++)
2090 if (*p < min_z) /* find match closest to screen */
2099 if (name_count > 0) /* take first hit */
2100 pp->mouse_gear_id = pnames[0];
2106 /* Window management, etc
2109 reshape_pinion (ModeInfo *mi, int width, int height)
2111 GLfloat h = (GLfloat) height / (GLfloat) width;
2112 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2114 glViewport (0, 0, (GLint) width, (GLint) height);
2116 glMatrixMode(GL_PROJECTION);
2118 gluPerspective (30.0, 1/h, 1.0, 100.0);
2120 glMatrixMode(GL_MODELVIEW);
2122 gluLookAt( 0.0, 0.0, 30.0,
2126 glClear(GL_COLOR_BUFFER_BIT);
2129 GLfloat render_width, layout_width;
2130 pp->vp_height = 1.0;
2133 pp->vp_left = -pp->vp_width/2;
2134 pp->vp_right = pp->vp_width/2;
2135 pp->vp_top = pp->vp_height/2;
2136 pp->vp_bottom = -pp->vp_height/2;
2138 render_width = pp->vp_width * 2;
2139 layout_width = pp->vp_width * 0.8 * gear_size;
2141 pp->render_left = -render_width/2;
2142 pp->render_right = render_width/2;
2144 pp->layout_left = pp->render_right;
2145 pp->layout_right = pp->layout_left + layout_width;
2151 pinion_handle_event (ModeInfo *mi, XEvent *event)
2153 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2155 if (event->xany.type == ButtonPress &&
2156 event->xbutton.button == Button1)
2158 pp->button_down_p = True;
2159 gltrackball_start (pp->trackball,
2160 event->xbutton.x, event->xbutton.y,
2161 MI_WIDTH (mi), MI_HEIGHT (mi));
2164 else if (event->xany.type == ButtonRelease &&
2165 event->xbutton.button == Button1)
2167 pp->button_down_p = False;
2170 else if (event->xany.type == ButtonPress &&
2171 (event->xbutton.button == Button4 ||
2172 event->xbutton.button == Button5))
2174 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
2175 !!event->xbutton.state);
2178 else if (event->xany.type == MotionNotify &&
2181 gltrackball_track (pp->trackball,
2182 event->xmotion.x, event->xmotion.y,
2183 MI_WIDTH (mi), MI_HEIGHT (mi));
2186 else if (event->xany.type == KeyPress)
2190 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2191 if (c == ' ' && debug_one_gear_p && pp->ngears)
2193 delete_gear (mi, pp->gears[0]);
2203 init_pinion (ModeInfo *mi)
2205 pinion_configuration *pp;
2206 int wire = MI_IS_WIREFRAME(mi);
2209 pps = (pinion_configuration *)
2210 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2212 fprintf(stderr, "%s: out of memory\n", progname);
2216 pp = &pps[MI_SCREEN(mi)];
2219 pp = &pps[MI_SCREEN(mi)];
2221 pp->glx_context = init_GL(mi);
2224 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2226 pp->title_list = glGenLists (1);
2232 plane_displacement *= gear_size;
2236 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2237 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2238 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2239 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2241 glEnable(GL_LIGHTING);
2242 glEnable(GL_LIGHT0);
2243 glEnable(GL_DEPTH_TEST);
2244 glEnable(GL_CULL_FACE);
2246 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2247 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2248 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2249 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2252 pp->trackball = gltrackball_init ();
2259 draw_pinion (ModeInfo *mi)
2261 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2262 Display *dpy = MI_DISPLAY(mi);
2263 Window window = MI_WINDOW(mi);
2264 Bool wire_p = MI_IS_WIREFRAME(mi);
2265 static int tick = 0;
2267 if (!pp->glx_context)
2270 if (!pp->button_down_p)
2272 if (!debug_one_gear_p || pp->ngears == 0)
2277 glShadeModel(GL_SMOOTH);
2279 glEnable(GL_DEPTH_TEST);
2280 glEnable(GL_NORMALIZE);
2281 glEnable(GL_CULL_FACE);
2283 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2287 gltrackball_rotate (pp->trackball);
2288 mi->polygon_count = 0;
2290 glScalef (16, 16, 16); /* map vp_width/height to the screen */
2292 if (debug_one_gear_p) /* zoom in */
2294 else if (debug_p) /* show the "visible" and "layout" areas */
2296 GLfloat ow = pp->layout_right - pp->render_left;
2297 GLfloat rw = pp->render_right - pp->render_left;
2298 GLfloat s = (pp->vp_width / ow) * 0.85;
2300 glTranslatef (-(ow - rw) / 2, 0, 0);
2305 glScalef (s, s, s); /* zoom in a little more */
2306 glRotatef (-35, 1, 0, 0); /* tilt back */
2307 glRotatef ( 8, 0, 1, 0); /* tilt left */
2308 glTranslatef (0.02, 0.1, 0); /* pan up */
2315 if (!wire_p) glDisable(GL_LIGHTING);
2316 glColor3f (0.6, 0, 0);
2317 glBegin(GL_LINE_LOOP);
2318 glVertex3f (pp->render_left, pp->vp_top, 0);
2319 glVertex3f (pp->render_right, pp->vp_top, 0);
2320 glVertex3f (pp->render_right, pp->vp_bottom, 0);
2321 glVertex3f (pp->render_left, pp->vp_bottom, 0);
2323 glColor3f (0.4, 0, 0);
2325 glVertex3f (pp->vp_left, pp->vp_top, 0);
2326 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
2327 glVertex3f (pp->vp_right, pp->vp_top, 0);
2328 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
2330 glColor3f (0, 0.4, 0);
2331 glBegin(GL_LINE_LOOP);
2332 glVertex3f (pp->layout_left, pp->vp_top, 0);
2333 glVertex3f (pp->layout_right, pp->vp_top, 0);
2334 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2335 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
2337 if (!wire_p) glEnable(GL_LIGHTING);
2340 if (tick++ > 10) /* only do this every N frames */
2343 find_mouse_gear (mi);
2349 glCallList (pp->title_list);
2351 if (mi->fps_p) do_fps (mi);
2354 glXSwapBuffers(dpy, window);