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 "*spinSpeed: " DEF_SPIN_SPEED " \n" \
33 "*scrollSpeed:" DEF_SCROLL_SPEED " \n" \
34 "*maxRPM: " DEF_MAX_RPM " \n" \
35 "*gearSize: " DEF_GEAR_SIZE " \n" \
36 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
37 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
38 "*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
41 #define countof(x) (sizeof((x))/sizeof((*x)))
44 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
46 #include "xlockmore.h"
48 #include "gltrackball.h"
52 #ifdef USE_GL /* whole file */
57 unsigned long id; /* unique name */
58 double x, y, z; /* position */
59 double r; /* radius of the gear, at middle of teeth */
60 double th; /* rotation (degrees) */
62 GLint nteeth; /* how many teeth */
63 double tooth_w, tooth_h; /* size of teeth */
65 double inner_r; /* radius of the (larger) inside hole */
66 double inner_r2; /* radius of the (smaller) inside hole, if any */
67 double inner_r3; /* yet another */
69 double thickness; /* height of the edge */
70 double thickness2; /* height of the (smaller) inside disc if any */
71 double thickness3; /* yet another */
72 int spokes; /* how many spokes inside, if any */
73 int nubs; /* how many little nubbly bits, if any */
74 double spoke_thickness; /* spoke versus hole */
75 GLfloat wobble; /* factory defect! */
76 int motion_blur_p; /* whether it's spinning too fast to draw */
77 int polygons; /* how many polys in this gear */
79 double ratio; /* gearing ratio with previous gears */
80 double rpm; /* approximate revolutions per minute */
82 Bool base_p; /* whether this gear begins a new train */
83 int coax_p; /* whether this is one of a pair of bound gears.
84 1 for first, 2 for second. */
85 double coax_thickness; /* thickness of the other gear in the pair */
86 enum { SMALL, MEDIUM, LARGE } size; /* Controls complexity of mesh. */
95 GLXContext *glx_context;
96 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
97 GLfloat vp_width, vp_height;
98 GLfloat render_left, render_right; /* area in which gears are displayed */
99 GLfloat layout_left, layout_right; /* layout region, on the right side */
105 trackball_state *trackball;
107 unsigned long mouse_gear_id;
109 XFontStruct *xfont1, *xfont2, *xfont3;
110 GLuint font1_dlist, font2_dlist, font3_dlist;
113 } pinion_configuration;
116 static pinion_configuration *pps = NULL;
117 static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
118 static GLfloat plane_displacement = 0.1; /* distance between coaxial gears */
120 static Bool verbose_p = False; /* print progress on stderr */
121 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
122 static Bool debug_p = False; /* render as flat schematic */
123 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
124 static Bool wire_all_p = False; /* in wireframe, do not abbreviate */
126 static int debug_size_failures; /* for debugging messages */
127 static int debug_position_failures;
128 static unsigned long current_length; /* gear count in current train */
129 static unsigned long current_blur_length; /* how long have we been blurring? */
132 static XrmOptionDescRec opts[] = {
133 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
134 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
135 { "-size", ".gearSize", XrmoptionSepArg, 0 },
136 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
137 { "-debug", ".debug", XrmoptionNoArg, "True" },
138 { "-verbose",".verbose", XrmoptionNoArg, "True" },
141 static argtype vars[] = {
142 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
143 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
144 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
145 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
146 {&debug_p, "debug", "Debug", "False", t_Bool},
147 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
150 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
157 load_fonts (ModeInfo *mi)
159 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
160 load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
161 load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
162 load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
167 static void rpm_string (double rpm, char *s);
170 new_label (ModeInfo *mi)
172 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
177 if (pp->mouse_gear_id)
178 for (i = 0; i < pp->ngears; i++)
179 if (pp->gears[i]->id == pp->mouse_gear_id)
189 sprintf (label, "%d teeth\n", g->nteeth);
190 rpm_string (g->rpm, label + strlen(label));
192 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
194 (g->size == SMALL ? "small" : g->size == MEDIUM ? "medium"
196 g->tooth_h * MI_HEIGHT(mi));
199 glNewList (pp->title_list, GL_COMPILE);
204 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
205 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
206 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
207 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
209 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
211 glColor3f (0.8, 0.8, 0);
212 print_gl_string (mi->dpy, f, fl,
213 mi->xgwa.width, mi->xgwa.height,
214 10, mi->xgwa.height - 10,
225 /* Find the gear in the scene that is farthest to the right or left.
228 farthest_gear (ModeInfo *mi, Bool left_p)
230 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
233 double x = (left_p ? 999999 : -999999);
234 for (i = 0; i < pp->ngears; i++)
236 gear *g = pp->gears[i];
237 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
238 if (left_p ? (x > gx) : (x < gx))
248 /* Compute the revolutions per minute of a gear.
251 compute_rpm (ModeInfo *mi, gear *g)
253 double fps, rpf, rps;
254 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
256 if (fps > 150) fps = 150; /* let's be reasonable... */
257 if (fps < 10) fps = 10;
259 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
260 rps = rpf * fps; /* rotations per second */
264 /* Prints the RPM into a string, doing fancy float formatting.
267 rpm_string (double rpm, char *s)
271 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
272 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
273 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
274 else sprintf (buf, "%.16f",rpm);
277 while (buf[L-1] == '0') buf[--L] = 0;
278 if (buf[L-1] == '.') buf[--L] = 0;
285 /* Which of the gear's inside rings is the biggest?
288 biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
290 double r0 = (g->r - g->tooth_h/2);
291 double r1 = g->inner_r;
292 double r2 = g->inner_r2;
293 double r3 = g->inner_r3;
294 double w1 = (r1 ? r0 - r1 : r0);
295 double w2 = (r2 ? r1 - r2 : 0);
296 double w3 = (r3 ? r2 - r3 : 0);
297 double h1 = g->thickness;
298 double h2 = g->thickness2;
299 double h3 = g->thickness3;
301 if (g->spokes) w2 = 0;
303 if (w1 > w2 && w1 > w3)
305 if (posP) *posP = (r0+r1)/2;
306 if (sizeP) *sizeP = w1;
307 if (heightP) *heightP = h1;
310 else if (w2 > w1 && w2 > w3)
312 if (posP) *posP = (r1+r2)/2;
313 if (sizeP) *sizeP = w2;
314 if (heightP) *heightP = h2;
319 if (posP) *posP = (r2+r3)/2;
320 if (sizeP) *sizeP = w3;
321 if (heightP) *heightP = h3;
331 /* Create and return a new gear sized for placement next to or on top of
332 the given parent gear (if any.) Returns 0 if out of memory.
335 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
337 gear *g = (gear *) calloc (1, sizeof (*g));
339 static unsigned long id = 0;
342 if (coaxial_p && !parent) abort();
348 if (loop_count > 1000)
349 /* The only time we loop in here is when making a coaxial gear, and
350 trying to pick a radius that is either significantly larger or
351 smaller than its parent. That shouldn't be hard, so something
352 must be really wrong if we can't do that in only a few tries.
356 /* Pick the size of the teeth.
358 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
360 g->tooth_w = parent->tooth_w;
361 g->tooth_h = parent->tooth_h;
362 g->thickness = parent->thickness;
363 g->thickness2 = parent->thickness2;
364 g->thickness3 = parent->thickness3;
366 else /* gears that begin trains get any size they want */
368 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
369 g->tooth_w = 0.007 * scale;
370 g->tooth_h = 0.005 * scale;
371 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
372 g->thickness2 = g->thickness / 4;
373 g->thickness3 = g->thickness;
376 /* Pick the number of teeth, and thus, the radius.
382 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
384 if (g->nteeth < 7 && (random() % 5) != 0)
385 goto AGAIN; /* Let's make very small tooth-counts more rare */
387 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
388 g->r = c / (M_PI * 2); /* c = 2 pi r */
394 if (! coaxial_p) break; /* yes */
395 if (g->nteeth == parent->nteeth) continue; /* ugly */
396 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
397 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
402 g->color[0] = 0.5 + frand(0.5);
403 g->color[1] = 0.5 + frand(0.5);
404 g->color[2] = 0.5 + frand(0.5);
407 g->color2[0] = g->color[0] * 0.85;
408 g->color2[1] = g->color[1] * 0.85;
409 g->color2[2] = g->color[2] * 0.85;
410 g->color2[3] = g->color[3];
413 /* Decide on shape of gear interior:
414 - just a ring with teeth;
415 - that, plus a thinner in-set "plate" in the middle;
416 - that, plus a thin raised "lip" on the inner plate;
417 - or, a wide lip (really, a thicker third inner plate.)
419 if ((random() % 10) == 0)
421 /* inner_r can go all the way in; there's no inset disc. */
422 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
428 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
429 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
430 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
433 if (g->inner_r2 > (g->r * 0.2))
435 int nn = (random() % 10);
437 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
438 else if (nn <= 7 && g->inner_r2 >= 0.1)
439 g->inner_r3 = g->inner_r2 - 0.01;
443 /* Coaxial gears need to have the same innermost hole size (for the axle.)
444 Use whichever of the two is smaller. (Modifies parent.)
448 double hole1 = (g->inner_r3 ? g->inner_r3 :
449 g->inner_r2 ? g->inner_r2 :
451 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
452 parent->inner_r2 ? parent->inner_r2 :
454 double hole = (hole1 < hole2 ? hole1 : hole2);
455 if (hole <= 0) abort();
457 if (g->inner_r3) g->inner_r3 = hole;
458 else if (g->inner_r2) g->inner_r2 = hole;
459 else g->inner_r = hole;
461 if (parent->inner_r3) parent->inner_r3 = hole;
462 else if (parent->inner_r2) parent->inner_r2 = hole;
463 else parent->inner_r = hole;
466 /* If we have three discs, sometimes make the middle disc be spokes.
468 if (g->inner_r3 && ((random() % 5) == 0))
470 g->spokes = 2 + BELLRAND (5);
471 g->spoke_thickness = 1 + frand(7.0);
472 if (g->spokes == 2 && g->spoke_thickness < 2)
473 g->spoke_thickness += 1;
476 /* Sometimes add little nubbly bits, if there is room.
481 biggest_ring (g, 0, &size, 0);
482 if (size > g->r * 0.2 && (random() % 5) == 0)
484 g->nubs = 1 + (random() % 16);
485 if (g->nubs > 8) g->nubs = 1;
489 if (g->inner_r3 > g->inner_r2) abort();
490 if (g->inner_r2 > g->inner_r) abort();
491 if (g->inner_r > g->r) abort();
493 /* Decide how complex the polygon model should be.
496 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
497 if (pix <= 2.5) g->size = SMALL;
498 else if (pix <= 3.5) g->size = MEDIUM;
499 else g->size = LARGE;
508 /* Given a newly-created gear, place it next to its parent in the scene,
509 with its teeth meshed and the proper velocity. Returns False if it
510 didn't work. (Call this a bunch of times until either it works, or
511 you decide it's probably not going to.)
514 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
516 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
518 /* If this gear takes up more than 1/3rd of the screen, it's no good.
520 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
521 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
523 if (verbose_p && debug_placement_p && 0)
525 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
527 (g->r + g->tooth_h), gear_size,
528 pp->vp_width, pp->vp_height);
529 debug_size_failures++;
533 /* Compute this gear's velocity.
537 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
538 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
542 g->ratio = parent->ratio; /* bound gears have the same ratio */
544 g->rpm = parent->rpm;
545 g->wobble = parent->wobble;
549 /* Gearing ratio is the ratio of the number of teeth to previous gear
550 (which is also the ratio of the circumferences.)
552 g->ratio = (double) parent->nteeth / (double) g->nteeth;
554 /* Set our initial rotation to match that of the previous gear,
555 multiplied by the gearing ratio. (This is finessed later,
556 once we know the exact position of the gear relative to its
559 g->th = -(parent->th * g->ratio);
561 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
563 double off = (180.0 / g->nteeth);
570 /* ratios are cumulative for all gears in the train. */
571 g->ratio *= parent->ratio;
575 /* Place the gear relative to the parent.
580 gear *rg = farthest_gear (mi, False);
581 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
582 if (right < pp->layout_left) /* place off screen */
583 right = pp->layout_left;
585 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
589 if (debug_one_gear_p)
594 double off = plane_displacement;
598 g->z = parent->z + (g->r > parent->r /* small gear on top */
601 if (parent->r > g->r) /* mark which is top and which is bottom */
605 parent->wobble = 0; /* looks bad when axle moves */
614 g->coax_thickness = parent->thickness;
615 parent->coax_thickness = g->thickness;
617 /* Don't let the train get too close to or far from the screen.
618 If we're getting too close, give up on this gear.
619 (But getting very far away is fine.)
621 if (g->z >= off * 4 ||
624 if (verbose_p && debug_placement_p)
625 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
627 debug_position_failures++;
631 else /* position it somewhere next to the parent. */
633 double r_off = parent->r + g->r;
636 if ((random() % 3) != 0)
637 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
639 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
641 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
642 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
645 /* If the angle we picked would have positioned this gear
646 more than halfway off screen, that's no good. */
647 if (g->y > pp->vp_top ||
648 g->y < pp->vp_bottom)
650 if (verbose_p && debug_placement_p)
651 fprintf (stderr, "%s: placement: out of bounds: %s\n",
652 progname, (g->y > pp->vp_top ? "top" : "bottom"));
653 debug_position_failures++;
657 /* avoid accidentally changing sign of "th" in the math below. */
658 g->th += (g->th > 0 ? 360 : -360);
660 /* Adjust the rotation of the gear so that its teeth line up with its
661 parent, based on the position of the gear and the current rotation
665 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
666 double g_c = 2 * M_PI * g->r; /* circumference of g */
668 double p_t = p_c * (angle/360.0); /* distance travelled along
669 circumference of parent when
670 moving "angle" degrees along
672 double g_rat = p_t / g_c; /* if travelling that distance
673 along circumference of g,
674 ratio of g's circumference
676 double g_th = 360.0 * g_rat; /* that ratio in degrees */
678 g->th += angle + g_th;
682 if (debug_one_gear_p)
688 /* If the position we picked for this gear would cause it to already
689 be visible on the screen, give up. This can happen when the train
690 is growing backwards, and we don't want to see gears flash into
693 if (g->x - g->r - g->tooth_h < pp->render_right)
695 if (verbose_p && debug_placement_p)
696 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
697 debug_position_failures++;
701 /* If the position we picked for this gear causes it to overlap
702 with any earlier gear in the train, give up.
707 for (i = pp->ngears-1; i >= 0; i--)
709 gear *og = pp->gears[i];
711 if (og == g) continue;
712 if (og == parent) continue;
713 if (g->z != og->z) continue; /* Ignore unless on same layer */
715 /* Collision detection without sqrt:
716 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
717 d < r1 + r2 d^2 < (r1 + r2)^2
719 if (((g->x - og->x) * (g->x - og->x) +
720 (g->y - og->y) * (g->y - og->y)) <
721 ((g->r + g->tooth_h + og->r + og->tooth_h) *
722 (g->r + g->tooth_h + og->r + og->tooth_h)))
724 if (verbose_p && debug_placement_p)
725 fprintf (stderr, "%s: placement: collision with %lu\n",
727 debug_position_failures++;
736 /* Make deeper gears be darker.
739 double depth = g->z / plane_displacement;
740 double brightness = 1 + (depth / 6);
742 if (brightness < limit) brightness = limit;
743 if (brightness > 1/limit) brightness = 1/limit;
744 g->color[0] *= brightness;
745 g->color[1] *= brightness;
746 g->color[2] *= brightness;
747 g->color2[0] *= brightness;
748 g->color2[1] *= brightness;
749 g->color2[2] *= brightness;
752 /* If a single frame of animation would cause the gear to rotate by
753 more than 1/2 the size of a single tooth, then it won't look right:
754 the gear will appear to be turning at some lower harmonic of its
758 double ratio = g->ratio * spin_speed;
759 double blur_limit = 180.0 / g->nteeth;
761 if (ratio > blur_limit)
762 g->motion_blur_p = 1;
766 /* ride until the wheels fall off... */
767 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
768 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
769 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
770 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
771 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
772 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
783 glDeleteLists (g->dlist, 1);
788 /* Make a new gear, place it next to its parent in the scene,
789 with its teeth meshed and the proper velocity. Returns the gear;
790 or 0 if it didn't work. (Call this a bunch of times until either
791 it works, or you decide it's probably not going to.)
794 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
796 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
803 if (loop_count >= 100)
811 g = new_gear (mi, parent, coaxial_p);
812 if (!g) return 0; /* out of memory? */
814 if (place_gear (mi, g, parent, coaxial_p))
820 /* We got a gear, and it is properly positioned.
821 Insert it in the scene.
823 if (pp->ngears + 2 >= pp->gears_size)
825 pp->gears_size += 100;
826 pp->gears = (gear **) realloc (pp->gears,
827 pp->gears_size * sizeof (*pp->gears));
830 fprintf (stderr, "%s: out of memory (%d gears)\n",
831 progname, pp->gears_size);
835 pp->gears[pp->ngears++] = g;
840 static void delete_gear (ModeInfo *mi, gear *g);
843 push_gear (ModeInfo *mi)
845 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
847 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
849 Bool tried_coaxial_p = False;
850 Bool coaxial_p = False;
851 Bool last_ditch_coax_p = False;
854 debug_size_failures = 0;
855 debug_position_failures = 0;
859 if (loop_count > 100) abort(); /* we're doomed! */
863 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
864 reset things to a sane velocity.
866 10,000 RPM at 30 FPS = 5.5 rotations per frame.
867 1,000 RPM at 30 FPS = 0.5 rotations per frame.
868 600 RPM at 30 FPS = 3 frames per rotation.
869 200 RPM at 30 FPS = 9 frames per rotation.
870 100 RPM at 30 FPS = 18 frames per rotation.
871 50 RPM at 30 FPS = 36 frames per rotation.
872 10 RPM at 30 FPS = 3 sec per rotation.
873 1 RPM at 30 FPS = 30 sec per rotation.
874 .5 RPM at 30 FPS = 1 min per rotation.
875 .1 RPM at 30 FPS = 5 min per rotation.
877 if (parent && parent->rpm > max_rpm)
882 rpm_string (parent->rpm, buf);
883 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
888 /* If the last N gears we've placed have all been motion-blurred, then
889 it's a safe guess that we've wandered off into the woods and aren't
890 coming back. Bail on this train.
892 if (current_blur_length >= 10)
895 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
901 /* Sometimes, try to make a coaxial gear.
903 if (parent && !parent->coax_p && (random() % 40) == 0)
905 tried_coaxial_p = True;
907 g = place_new_gear (mi, parent, coaxial_p);
910 /* Try to make a regular gear.
915 g = place_new_gear (mi, parent, coaxial_p);
918 /* If we couldn't make a regular gear, then try to make a coxial gear
919 (unless we already tried that.)
921 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
923 tried_coaxial_p = True;
925 g = place_new_gear (mi, parent, coaxial_p);
927 last_ditch_coax_p = True;
930 /* If we couldn't do that either, then the train has hit a dead end:
937 fprintf (stderr, "%s: dead end!\n\n", progname);
939 g = place_new_gear (mi, parent, coaxial_p);
944 /* Unable to make/place any gears at all!
945 This can happen if we've backed ourself into a corner very near
946 the top-right or bottom-right corner of the growth zone.
947 It's time to add a gear, but there's no room to add one!
948 In that case, let's just wipe all the gears that are in the
949 growth zone and try again.
953 if (verbose_p && debug_placement_p)
955 "%s: placement: resetting growth zone! "
956 "failed: %d size, %d pos\n",
958 debug_size_failures, debug_position_failures);
959 for (i = pp->ngears-1; i >= 0; i--)
961 gear *g = pp->gears[i];
962 if (g->x - g->r - g->tooth_h < pp->render_left)
970 if (g->x != parent->x) abort();
971 if (g->y != parent->y) abort();
972 if (g->z == parent->z) abort();
973 if (g->r == parent->r) abort();
974 if (g->th != parent->th) abort();
975 if (g->ratio != parent->ratio) abort();
976 if (g->rpm != parent->rpm) abort();
981 fprintf (stderr, "%s: %5lu ", progname, g->id);
983 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
984 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
985 g->coax_p ? '1' : ' '),
988 fprintf (stderr, " %2d%%",
989 (int) (g->r * 2 * 100 / pp->vp_height));
990 fprintf (stderr, " %2d teeth", g->nteeth);
991 fprintf (stderr, " %3.0f rpm;", g->rpm);
994 char buf1[50], buf2[50], buf3[100];
995 *buf1 = 0; *buf2 = 0; *buf3 = 0;
996 if (debug_size_failures)
997 sprintf (buf1, "%3d sz", debug_size_failures);
998 if (debug_position_failures)
999 sprintf (buf2, "%2d pos", debug_position_failures);
1001 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
1002 fprintf (stderr, "%-21s", buf3);
1005 if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
1006 fprintf (stderr, "\n");
1014 if (g->motion_blur_p)
1015 current_blur_length++;
1017 current_blur_length = 0;
1022 /* Remove the given gear from the scene and free it.
1025 delete_gear (ModeInfo *mi, gear *g)
1027 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1030 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
1031 if (pp->gears[i] == g) break;
1032 if (pp->gears[i] != g) abort();
1034 for (; i < pp->ngears-1; i++) /* pull later entries forward */
1035 pp->gears[i] = pp->gears[i+1];
1042 /* Update the position of each gear in the scene.
1045 scroll_gears (ModeInfo *mi)
1047 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1050 for (i = 0; i < pp->ngears; i++)
1051 pp->gears[i]->x -= (scroll_speed * 0.002);
1053 /* if the right edge of any gear is off screen to the left, delete it.
1055 for (i = pp->ngears-1; i >= 0; i--)
1057 gear *g = pp->gears[i];
1058 if (g->x + g->r + g->tooth_h < pp->render_left)
1059 delete_gear (mi, g);
1062 /* if the right edge of the last-added gear is left of the right edge
1063 of the layout area, add another gear.
1068 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1069 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1074 if (debug_one_gear_p) break;
1078 if (i > 1 && verbose_p)
1079 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1084 /* Update the rotation of each gear in the scene.
1087 spin_gears (ModeInfo *mi)
1089 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1092 for (i = 0; i < pp->ngears; i++)
1094 gear *g = pp->gears[i];
1095 double off = (g->ratio * spin_speed);
1105 /* Run the animation fast (without displaying anything) until the first
1106 gear is just about to come on screen. This is to avoid a big delay
1107 with a blank screen when -scroll is low.
1112 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1113 if (debug_one_gear_p) return;
1116 gear *g = farthest_gear (mi, True);
1117 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1125 /* Rendering the 3D objects into the scene.
1129 /* Draws an uncapped tube of the given radius extending from top to bottom,
1130 with faces pointing either in or out.
1133 draw_ring (ModeInfo *mi, int segments,
1134 GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1138 Bool wire_p = MI_IS_WIREFRAME(mi);
1139 GLfloat width = M_PI * 2 / segments;
1143 glFrontFace (in_p ? GL_CCW : GL_CW);
1144 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1145 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1147 GLfloat th = i * width;
1148 GLfloat cth = cos(th);
1149 GLfloat sth = sin(th);
1151 glNormal3f (-cth, -sth, 0);
1153 glNormal3f (cth, sth, 0);
1154 glVertex3f (cth * r, sth * r, top);
1155 glVertex3f (cth * r, sth * r, bottom);
1163 glBegin (GL_LINE_LOOP);
1164 for (i = 0; i < segments; i++)
1166 GLfloat th = i * width;
1167 glVertex3f (cos(th) * r, sin(th) * r, top);
1170 glBegin (GL_LINE_LOOP);
1171 for (i = 0; i < segments; i++)
1173 GLfloat th = i * width;
1174 glVertex3f (cos(th) * r, sin(th) * r, bottom);
1183 /* Draws a donut-shaped disc between the given radii,
1184 with faces pointing either up or down.
1185 The first radius may be 0, in which case, a filled disc is drawn.
1188 draw_disc (ModeInfo *mi, int segments,
1189 GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1193 Bool wire_p = MI_IS_WIREFRAME(mi);
1194 GLfloat width = M_PI * 2 / segments;
1196 if (ra < 0) abort();
1197 if (rb <= 0) abort();
1200 glFrontFace (up_p ? GL_CW : GL_CCW);
1202 glFrontFace (up_p ? GL_CCW : GL_CW);
1205 glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1207 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1209 glNormal3f (0, 0, (up_p ? -1 : 1));
1211 if (ra == 0 && !wire_p)
1212 glVertex3f (0, 0, z);
1214 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1216 GLfloat th = i * width;
1217 GLfloat cth = cos(th);
1218 GLfloat sth = sin(th);
1219 if (wire_p || ra != 0)
1220 glVertex3f (cth * ra, sth * ra, z);
1221 glVertex3f (cth * rb, sth * rb, z);
1229 /* Draws N thick radial lines between the given radii,
1230 with faces pointing either up or down.
1233 draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1234 GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1238 Bool wire_p = MI_IS_WIREFRAME(mi);
1241 int insegs, outsegs;
1245 if (ra <= 0 || rb <= 0) abort();
1249 while (segments2 < segments) /* need a multiple of N >= segments */
1250 segments2 += n; /* (yes, this is a moronic way to find that) */
1252 insegs = ((float) (segments2 / n) + 0.5) / thickness;
1253 outsegs = (segments2 / n) - insegs;
1254 if (insegs <= 0) insegs = 1;
1255 if (outsegs <= 0) outsegs = 1;
1257 segments2 = (insegs + outsegs) * n;
1258 width = M_PI * 2 / segments2;
1262 for (i = 0; i < segments2; i++, tick++)
1264 GLfloat th1 = i * width;
1265 GLfloat th2 = th1 + width;
1266 GLfloat cth1 = cos(th1);
1267 GLfloat sth1 = sin(th1);
1268 GLfloat cth2 = cos(th2);
1269 GLfloat sth2 = sin(th2);
1272 int changed = (i == 0);
1274 if (state == 0 && tick == insegs)
1275 tick = 0, state = 1, changed = 1;
1276 else if (state == 1 && tick == outsegs)
1277 tick = 0, state = 0, changed = 1;
1279 if ((state == 1 || /* in */
1280 (state == 0 && changed)) &&
1281 (!wire_p || wire_all_p))
1284 glFrontFace (GL_CCW);
1285 glBegin (wire_p ? GL_LINES : GL_QUADS);
1286 glNormal3f (0, 0, -1);
1287 glVertex3f (cth1 * ra, sth1 * ra, z1);
1288 glVertex3f (cth1 * rb, sth1 * rb, z1);
1289 glVertex3f (cth2 * rb, sth2 * rb, z1);
1290 glVertex3f (cth2 * ra, sth2 * ra, z1);
1295 glFrontFace (GL_CW);
1296 glBegin (wire_p ? GL_LINES : GL_QUADS);
1297 glNormal3f (0, 0, 1);
1298 glVertex3f (cth1 * ra, sth1 * ra, z2);
1299 glVertex3f (cth1 * rb, sth1 * rb, z2);
1300 glVertex3f (cth2 * rb, sth2 * rb, z2);
1301 glVertex3f (cth2 * ra, sth2 * ra, z2);
1306 if (state == 1 && changed) /* entering "in" state */
1309 glFrontFace (GL_CW);
1310 glBegin (wire_p ? GL_LINES : GL_QUADS);
1311 do_normal (cth1 * rb, sth1 * rb, z1,
1312 cth1 * ra, sth1 * ra, z1,
1313 cth1 * rb, sth1 * rb, z2);
1314 glVertex3f (cth1 * ra, sth1 * ra, z1);
1315 glVertex3f (cth1 * rb, sth1 * rb, z1);
1316 glVertex3f (cth1 * rb, sth1 * rb, z2);
1317 glVertex3f (cth1 * ra, sth1 * ra, z2);
1322 if (state == 0 && changed) /* entering "out" state */
1325 glFrontFace (GL_CCW);
1326 glBegin (wire_p ? GL_LINES : GL_QUADS);
1327 do_normal (cth2 * ra, sth2 * ra, z1,
1328 cth2 * rb, sth2 * rb, z1,
1329 cth2 * rb, sth2 * rb, z2);
1330 glVertex3f (cth2 * ra, sth2 * ra, z1);
1331 glVertex3f (cth2 * rb, sth2 * rb, z1);
1332 glVertex3f (cth2 * rb, sth2 * rb, z2);
1333 glVertex3f (cth2 * ra, sth2 * ra, z2);
1345 /* Draws some bumps (embedded cylinders) on the gear.
1348 draw_gear_nubs (ModeInfo *mi, gear *g)
1350 Bool wire_p = MI_IS_WIREFRAME(mi);
1353 int steps = (g->size != LARGE ? 5 : 20);
1354 double r, size, height;
1359 if (! g->nubs) return 0;
1361 which = biggest_ring (g, &r, &size, &height);
1365 cc = (which == 1 ? g->color : g->color2);
1366 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1368 width = M_PI * 2 / g->nubs;
1369 off = M_PI / (g->nteeth * 2); /* align first nub with a tooth */
1371 for (i = 0; i < g->nubs; i++)
1373 GLfloat th = (i * width) + off;
1375 glTranslatef (cos(th) * r, sin(th) * r, 0);
1377 if (wire_p && !wire_all_p)
1378 polys += draw_ring (mi, (g->size == LARGE ? steps/2 : steps),
1382 polys += draw_disc (mi, steps, 0, size, -height, True);
1383 polys += draw_disc (mi, steps, 0, size, height, False);
1384 polys += draw_ring (mi, steps, size, -height, height, False);
1393 /* Draws a much simpler representation of a gear.
1396 draw_gear_schematic (ModeInfo *mi, gear *g)
1398 Bool wire_p = MI_IS_WIREFRAME(mi);
1401 GLfloat width = M_PI * 2 / g->nteeth;
1403 if (!wire_p) glDisable(GL_LIGHTING);
1404 glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1407 for (i = 0; i < g->nteeth; i++)
1409 GLfloat th = (i * width) + (width/4);
1410 glVertex3f (0, 0, -g->thickness/2);
1411 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1416 glBegin (GL_LINE_LOOP);
1417 for (i = 0; i < g->nteeth; i++)
1419 GLfloat th = (i * width) + (width/4);
1420 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1425 if (!wire_p) glEnable(GL_LIGHTING);
1430 /* Renders all the interior (non-toothy) parts of a gear:
1431 the discs, axles, etc.
1434 draw_gear_interior (ModeInfo *mi, gear *g)
1436 Bool wire_p = MI_IS_WIREFRAME(mi);
1439 int steps = g->nteeth * 2;
1440 if (steps < 10) steps = 10;
1441 if ((wire_p && !wire_all_p) || g->size != LARGE) steps /= 2;
1442 if (g->size != LARGE && steps > 16) steps = 16;
1444 /* ring 1 (facing in) is done in draw_gear_teeth */
1446 /* ring 2 (facing in) and disc 2
1450 GLfloat ra = g->inner_r * 1.04; /* slightly larger than inner_r */
1451 GLfloat rb = g->inner_r2; /* since the points don't line up */
1452 GLfloat za = -g->thickness2/2;
1453 GLfloat zb = g->thickness2/2;
1455 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1457 if ((g->coax_p != 1 && !g->inner_r3) ||
1458 (wire_p && wire_all_p))
1459 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1461 if (wire_p && wire_all_p)
1462 polys += draw_ring (mi, steps, ra, za, zb, True); /* ring facing in */
1465 polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1466 steps, ra, rb, za, zb);
1467 else if (!wire_p || wire_all_p)
1469 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1470 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1474 /* ring 3 (facing in and out) and disc 3
1478 GLfloat ra = g->inner_r2;
1479 GLfloat rb = g->inner_r3;
1480 GLfloat za = -g->thickness3/2;
1481 GLfloat zb = g->thickness3/2;
1483 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1485 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1487 if (g->coax_p != 1 || (wire_p && wire_all_p))
1488 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1490 if (!wire_p || wire_all_p)
1492 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1493 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1501 GLfloat cap_height = g->coax_thickness/3;
1503 GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1504 g->inner_r2 ? g->inner_r2 :
1506 GLfloat za = -(g->thickness/2 + cap_height);
1507 GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
1509 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1511 if (wire_p && !wire_all_p) steps /= 2;
1513 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1515 if (!wire_p || wire_all_p)
1517 polys += draw_disc (mi, steps, 0, ra, za, True); /* top plate */
1518 polys += draw_disc (mi, steps, 0, ra, zb, False); /* bottom plate */
1525 /* gear_teeth_geometry computes the vertices and normals of the teeth
1526 of a gear. This is the heavy lifting: there are a ton of polygons
1527 around the perimiter of a gear, and the normals are difficult (not
1528 radial or right angles.)
1530 It would be nice if we could cache this, but the numbers are
1531 different for essentially every gear:
1533 - Every gear has a different inner_r, so the vertices of the
1534 inner ring (and thus, the triangle fans on the top and bottom
1535 faces) are different in a non-scalable way.
1537 - If the ratio between tooth_w and tooth_h changes, the normals
1538 on the outside edges of the teeth are invalid (this can happen
1539 every time we start a new train.)
1541 So instead, we rely on OpenGL display lists to do the cacheing for
1542 us -- we only compute all these normals once per gear, instead of
1543 once per gear per frame.
1549 XYZ *fnormals; /* face normals */
1550 XYZ *pnormals; /* point normals */
1555 tooth_normals (tooth_face *f)
1559 /* Compute the face normals for each facet. */
1560 for (i = 0; i < f->npoints; i++)
1564 int b = (i == f->npoints-1 ? 0 : i+1);
1569 f->fnormals[i] = calc_normal (p1, p2, p3);
1572 /* From the face normals, compute the vertex normals
1573 (by averaging the normals of adjascent faces.)
1575 for (i = 0; i < f->npoints; i++)
1577 int a = (i == 0 ? f->npoints-1 : i-1);
1579 XYZ n1 = f->fnormals[a]; /* normal of [i-1 - i] face */
1580 XYZ n2 = f->fnormals[b]; /* normal of [i - i+1] face */
1581 f->pnormals[i].x = (n1.x + n2.x) / 2;
1582 f->pnormals[i].y = (n1.y + n2.y) / 2;
1583 f->pnormals[i].z = (n1.z + n2.z) / 2;
1589 gear_teeth_geometry (ModeInfo *mi, gear *g,
1590 tooth_face *orim, /* outer rim (the teeth) */
1591 tooth_face *irim) /* inner rim (the hole) */
1594 int ppt = 9; /* max points per tooth */
1595 GLfloat width = M_PI * 2 / g->nteeth;
1596 GLfloat rh = g->tooth_h;
1599 /* Approximate shape of an "involute" gear tooth.
1602 th0 th1 th2 th3 th4 th5 th6 th7 th8 th9 th10
1603 : : : : : : : : : : :
1604 : : : : : : : : : : :
1605 r0 ........:..:..:...___________...:..:..:......:......:..
1606 : : : /: : :\ : : : : :
1607 : : : / : : : \ : : : : :
1608 : : :/ : : : \: : : : :
1609 r1 ........:.....@...:....:....:...@..:..:......:......:..
1610 : : @: : : : :@ : : : :
1611 (R) ...........:...@.:...:....:....:...:.@..........:......:......
1612 : :@ : : : : : @: : : :
1613 r2 ........:..@..:...:....:....:...:..@:........:......:..
1614 : /: : : : : : :\ : : :
1615 :/ : : : : : : : \: : : /
1616 r3 ......__/..:..:...:....:....:...:..:..\______________/
1617 : : : : : : : : : : :
1618 | : : : : : : : | : :
1619 : : : : : : : : : : :
1620 | : : : : : : : | : :
1621 r4 ......__:_____________________________:________________
1628 r[0] = R + (rh * 0.5);
1629 r[1] = R + (rh * 0.25);
1630 r[2] = R - (r[1]-R);
1631 r[3] = R - (r[0]-R);
1634 th[0] = -tw * (g->size == SMALL ? 0.5 : g->size == MEDIUM ? 0.41 : 0.45);
1636 th[2] = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
1637 th[3] = -tw * (g->size == MEDIUM ? 0.1 : 0.04);
1644 th[10]= th[0] + width;
1647 orim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
1648 orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
1649 orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
1652 irim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
1653 irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
1654 irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
1656 if (!orim->points || !orim->pnormals || !orim->fnormals ||
1657 !irim->points || !irim->pnormals || !irim->fnormals)
1659 fprintf (stderr, "%s: out of memory\n", progname);
1663 /* First, compute the coordinates of every point used for every tooth.
1665 for (i = 0; i < g->nteeth; i++)
1667 GLfloat TH = (i * width) + (width/4);
1670 # define PUSH(OPR,IPR,PTH) \
1671 orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
1672 orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
1674 irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
1675 irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
1678 if (g->size == SMALL)
1680 PUSH(3, 4, 0); /* tooth left 1 */
1681 PUSH(0, 4, 4); /* tooth top middle */
1683 else if (g->size == MEDIUM)
1685 PUSH(3, 4, 0); /* tooth left 1 */
1686 PUSH(0, 4, 3); /* tooth top left */
1687 PUSH(0, 4, 5); /* tooth top right */
1688 PUSH(3, 4, 8); /* tooth right 3 */
1690 else if (g->size == LARGE)
1692 PUSH(3, 4, 0); /* tooth left 1 */
1693 PUSH(2, 4, 1); /* tooth left 2 */
1694 PUSH(1, 4, 2); /* tooth left 3 */
1695 PUSH(0, 4, 3); /* tooth top left */
1696 PUSH(0, 4, 5); /* tooth top right */
1697 PUSH(1, 4, 6); /* tooth right 1 */
1698 PUSH(2, 4, 7); /* tooth right 2 */
1699 PUSH(3, 4, 8); /* tooth right 3 */
1700 PUSH(3, 4, 9); /* gap top */
1706 if (i == 0 && orim->npoints > ppt) abort(); /* go update "ppt"! */
1709 tooth_normals (orim);
1710 tooth_normals (irim);
1714 /* Renders all teeth of a gear.
1717 draw_gear_teeth (ModeInfo *mi, gear *g)
1719 Bool wire_p = MI_IS_WIREFRAME(mi);
1720 Bool show_normals_p = False;
1724 GLfloat z1 = -g->thickness/2;
1725 GLfloat z2 = g->thickness/2;
1727 tooth_face orim, irim;
1728 gear_teeth_geometry (mi, g, &orim, &irim);
1730 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1732 /* Draw the outer rim (the teeth)
1733 (In wire mode, this draws just the upright lines.)
1735 glFrontFace (GL_CW);
1736 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1737 for (i = 0; i < orim.npoints; i++)
1739 glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
1740 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1741 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1743 /* Show the face normal vectors */
1744 if (wire_p && show_normals_p)
1746 XYZ n = orim.fnormals[i];
1748 int b = (i == orim.npoints-1 ? 0 : i+1);
1749 GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
1750 GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
1751 GLfloat z = (z1 + z2) / 2;
1752 glVertex3f (x, y, z);
1753 glVertex3f (x + n.x, y + n.y, z);
1756 /* Show the vertex normal vectors */
1757 if (wire_p && show_normals_p)
1759 XYZ n = orim.pnormals[i];
1760 GLfloat x = orim.points[i].x;
1761 GLfloat y = orim.points[i].y;
1762 GLfloat z = (z1 + z2) / 2;
1763 glVertex3f (x, y, z);
1764 glVertex3f (x + n.x, y + n.y, z);
1768 if (!wire_p) /* close the quad loop */
1770 glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
1771 glVertex3f (orim.points[0].x, orim.points[0].y, z1);
1772 glVertex3f (orim.points[0].x, orim.points[0].y, z2);
1774 polys += orim.npoints;
1777 /* Draw the outer rim circles, in wire mode */
1780 glBegin (GL_LINE_LOOP);
1781 for (i = 0; i < orim.npoints; i++)
1782 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1784 glBegin (GL_LINE_LOOP);
1785 for (i = 0; i < orim.npoints; i++)
1786 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1791 /* Draw the inner rim (the hole)
1792 (In wire mode, this draws just the upright lines.)
1794 glFrontFace (GL_CCW);
1795 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1796 for (i = 0; i < irim.npoints; i++)
1798 glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
1799 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1800 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1802 /* Show the face normal vectors */
1803 if (wire_p && show_normals_p)
1805 XYZ n = irim.fnormals[i];
1807 int b = (i == irim.npoints-1 ? 0 : i+1);
1808 GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
1809 GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
1810 GLfloat z = (z1 + z2) / 2;
1811 glVertex3f (x, y, z);
1812 glVertex3f (x - n.x, y - n.y, z);
1815 /* Show the vertex normal vectors */
1816 if (wire_p && show_normals_p)
1818 XYZ n = irim.pnormals[i];
1819 GLfloat x = irim.points[i].x;
1820 GLfloat y = irim.points[i].y;
1821 GLfloat z = (z1 + z2) / 2;
1822 glVertex3f (x, y, z);
1823 glVertex3f (x - n.x, y - n.y, z);
1827 if (!wire_p) /* close the quad loop */
1829 glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
1830 glVertex3f (irim.points[0].x, irim.points[0].y, z1);
1831 glVertex3f (irim.points[0].x, irim.points[0].y, z2);
1833 polys += irim.npoints;
1836 /* Draw the inner rim circles, in wire mode
1840 glBegin (GL_LINE_LOOP);
1841 for (i = 0; i < irim.npoints; i++)
1842 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1844 glBegin (GL_LINE_LOOP);
1845 for (i = 0; i < irim.npoints; i++)
1846 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1851 /* Draw the side (the flat bit)
1853 if (!wire_p || wire_all_p)
1856 if (irim.npoints != orim.npoints) abort();
1857 for (z = z1; z <= z2; z += z2-z1)
1859 glFrontFace (z == z1 ? GL_CCW : GL_CW);
1860 glNormal3f (0, 0, z);
1861 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1862 for (i = 0; i < orim.npoints; i++)
1864 glVertex3f (orim.points[i].x, orim.points[i].y, z);
1865 glVertex3f (irim.points[i].x, irim.points[i].y, z);
1867 if (!wire_p) /* close the quad loop */
1869 glVertex3f (orim.points[0].x, orim.points[0].y, z);
1870 glVertex3f (irim.points[0].x, irim.points[0].y, z);
1872 polys += orim.npoints;
1878 free (irim.fnormals);
1879 free (irim.pnormals);
1882 free (orim.fnormals);
1883 free (orim.pnormals);
1889 /* Render one gear, unrotated at 0,0.
1892 draw_gear_1 (ModeInfo *mi, gear *g)
1894 Bool wire_p = MI_IS_WIREFRAME(mi);
1897 static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
1898 static GLfloat shiny = 128.0;
1900 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
1901 glMateriali (GL_FRONT, GL_SHININESS, shiny);
1902 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1903 glColor3f (g->color[0], g->color[1], g->color[2]);
1905 if (debug_p && wire_p)
1906 polys += draw_gear_schematic (mi, g);
1910 glRotatef (g->wobble, 1, 0, 0);
1911 polys += draw_gear_teeth (mi, g);
1912 polys += draw_gear_interior (mi, g);
1913 polys += draw_gear_nubs (mi, g);
1920 /* Render one gear in the proper position, creating the gear's
1921 display list first if necessary.
1924 draw_gear (ModeInfo *mi, int which)
1926 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1927 gear *g = pp->gears[which];
1930 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1931 g->x - g->r - g->tooth_h <= pp->render_right);
1933 if (!visible_p && !debug_p)
1938 g->dlist = glGenLists (1);
1941 /* I don't know how many display lists a GL implementation
1942 is supposed to provide, but hopefully it's more than
1943 "a few hundred", or we'll be in trouble...
1945 check_gl_error ("glGenLists");
1949 glNewList (g->dlist, GL_COMPILE);
1950 g->polygons = draw_gear_1 (mi, g);
1956 glTranslatef (g->x, g->y, g->z);
1958 if (g->motion_blur_p && !pp->button_down_p)
1960 /* If we're in motion-blur mode, then draw the gear so that each
1961 frame rotates it by exactly one half tooth-width, so that it
1962 looks flickery around the edges. But, revert to the normal
1963 way when the mouse button is down lest the user see overlapping
1966 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1972 glRotatef (th, 0, 0, 1);
1977 mi->polygon_count += draw_gear_schematic (mi, g);
1980 glCallList (g->dlist);
1981 mi->polygon_count += g->polygons;
1989 /* Render all gears.
1992 draw_gears (ModeInfo *mi)
1994 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1995 Bool wire_p = MI_IS_WIREFRAME(mi);
1998 glColor4f (1, 1, 0.8, 1);
2002 for (i = 0; i < pp->ngears; i++)
2005 /* draw a line connecting gears that are, uh, geared. */
2008 static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2010 GLfloat ox=0, oy=0, oz=0;
2012 if (!wire_p) glDisable(GL_LIGHTING);
2013 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2014 glColor3f (color[0], color[1], color[2]);
2016 for (i = 0; i < pp->ngears; i++)
2018 gear *g = pp->gears[i];
2019 glBegin(GL_LINE_STRIP);
2020 glVertex3f (g->x, g->y, g->z - off);
2021 glVertex3f (g->x, g->y, g->z + off);
2022 if (i > 0 && !g->base_p)
2023 glVertex3f (ox, oy, oz + off);
2029 if (!wire_p) glEnable(GL_LIGHTING);
2034 /* Mouse hit detection
2037 find_mouse_gear (ModeInfo *mi)
2039 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2040 int screen_width = MI_WIDTH (mi);
2041 int screen_height = MI_HEIGHT (mi);
2042 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2046 pp->mouse_gear_id = 0;
2048 /* Poll mouse position */
2053 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2054 &r, &c, &rx, &ry, &x, &y, &m);
2057 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2058 return; /* out of window */
2060 /* Run OpenGL hit detector */
2065 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
2066 glRenderMode (GL_SELECT);
2067 glMatrixMode (GL_PROJECTION);
2070 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
2071 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2072 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
2073 glMatrixMode (GL_MODELVIEW);
2075 draw_gears (mi); /* render into "select" buffer */
2077 glMatrixMode (GL_PROJECTION); /* restore old vp */
2079 glMatrixMode (GL_MODELVIEW);
2081 hits = glRenderMode (GL_RENDER); /* done selecting */
2086 GLuint name_count = 0;
2087 GLuint *p = (GLuint *) selbuf;
2091 for (i = 0; i < hits; i++)
2094 if (*p < min_z) /* find match closest to screen */
2103 if (name_count > 0) /* take first hit */
2104 pp->mouse_gear_id = pnames[0];
2110 /* Window management, etc
2113 reshape_pinion (ModeInfo *mi, int width, int height)
2115 GLfloat h = (GLfloat) height / (GLfloat) width;
2116 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2118 glViewport (0, 0, (GLint) width, (GLint) height);
2120 glMatrixMode(GL_PROJECTION);
2122 gluPerspective (30.0, 1/h, 1.0, 100.0);
2124 glMatrixMode(GL_MODELVIEW);
2126 gluLookAt( 0.0, 0.0, 30.0,
2130 glClear(GL_COLOR_BUFFER_BIT);
2133 GLfloat render_width, layout_width;
2134 pp->vp_height = 1.0;
2137 pp->vp_left = -pp->vp_width/2;
2138 pp->vp_right = pp->vp_width/2;
2139 pp->vp_top = pp->vp_height/2;
2140 pp->vp_bottom = -pp->vp_height/2;
2142 render_width = pp->vp_width * 2;
2143 layout_width = pp->vp_width * 0.8 * gear_size;
2145 pp->render_left = -render_width/2;
2146 pp->render_right = render_width/2;
2148 pp->layout_left = pp->render_right;
2149 pp->layout_right = pp->layout_left + layout_width;
2155 pinion_handle_event (ModeInfo *mi, XEvent *event)
2157 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2159 if (event->xany.type == ButtonPress &&
2160 event->xbutton.button == Button1)
2162 pp->button_down_p = True;
2163 gltrackball_start (pp->trackball,
2164 event->xbutton.x, event->xbutton.y,
2165 MI_WIDTH (mi), MI_HEIGHT (mi));
2168 else if (event->xany.type == ButtonRelease &&
2169 event->xbutton.button == Button1)
2171 pp->button_down_p = False;
2174 else if (event->xany.type == ButtonPress &&
2175 (event->xbutton.button == Button4 ||
2176 event->xbutton.button == Button5))
2178 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
2179 !!event->xbutton.state);
2182 else if (event->xany.type == MotionNotify &&
2185 gltrackball_track (pp->trackball,
2186 event->xmotion.x, event->xmotion.y,
2187 MI_WIDTH (mi), MI_HEIGHT (mi));
2190 else if (event->xany.type == KeyPress)
2194 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2195 if (c == ' ' && debug_one_gear_p && pp->ngears)
2197 delete_gear (mi, pp->gears[0]);
2207 init_pinion (ModeInfo *mi)
2209 pinion_configuration *pp;
2210 int wire = MI_IS_WIREFRAME(mi);
2213 pps = (pinion_configuration *)
2214 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2216 fprintf(stderr, "%s: out of memory\n", progname);
2220 pp = &pps[MI_SCREEN(mi)];
2223 pp = &pps[MI_SCREEN(mi)];
2225 pp->glx_context = init_GL(mi);
2228 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2230 pp->title_list = glGenLists (1);
2236 plane_displacement *= gear_size;
2240 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2241 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2242 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2243 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2245 glEnable(GL_LIGHTING);
2246 glEnable(GL_LIGHT0);
2247 glEnable(GL_DEPTH_TEST);
2248 glEnable(GL_CULL_FACE);
2250 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2251 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2252 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2253 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2256 pp->trackball = gltrackball_init ();
2263 draw_pinion (ModeInfo *mi)
2265 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2266 Display *dpy = MI_DISPLAY(mi);
2267 Window window = MI_WINDOW(mi);
2268 Bool wire_p = MI_IS_WIREFRAME(mi);
2269 static int tick = 0;
2271 if (!pp->glx_context)
2274 if (!pp->button_down_p)
2276 if (!debug_one_gear_p || pp->ngears == 0)
2281 glShadeModel(GL_SMOOTH);
2283 glEnable(GL_DEPTH_TEST);
2284 glEnable(GL_NORMALIZE);
2285 glEnable(GL_CULL_FACE);
2287 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2291 gltrackball_rotate (pp->trackball);
2292 mi->polygon_count = 0;
2294 glScalef (16, 16, 16); /* map vp_width/height to the screen */
2296 if (debug_one_gear_p) /* zoom in */
2298 else if (debug_p) /* show the "visible" and "layout" areas */
2300 GLfloat ow = pp->layout_right - pp->render_left;
2301 GLfloat rw = pp->render_right - pp->render_left;
2302 GLfloat s = (pp->vp_width / ow) * 0.85;
2304 glTranslatef (-(ow - rw) / 2, 0, 0);
2309 glScalef (s, s, s); /* zoom in a little more */
2310 glRotatef (-35, 1, 0, 0); /* tilt back */
2311 glRotatef ( 8, 0, 1, 0); /* tilt left */
2312 glTranslatef (0.02, 0.1, 0); /* pan up */
2319 if (!wire_p) glDisable(GL_LIGHTING);
2320 glColor3f (0.6, 0, 0);
2321 glBegin(GL_LINE_LOOP);
2322 glVertex3f (pp->render_left, pp->vp_top, 0);
2323 glVertex3f (pp->render_right, pp->vp_top, 0);
2324 glVertex3f (pp->render_right, pp->vp_bottom, 0);
2325 glVertex3f (pp->render_left, pp->vp_bottom, 0);
2327 glColor3f (0.4, 0, 0);
2329 glVertex3f (pp->vp_left, pp->vp_top, 0);
2330 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
2331 glVertex3f (pp->vp_right, pp->vp_top, 0);
2332 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
2334 glColor3f (0, 0.4, 0);
2335 glBegin(GL_LINE_LOOP);
2336 glVertex3f (pp->layout_left, pp->vp_top, 0);
2337 glVertex3f (pp->layout_right, pp->vp_top, 0);
2338 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2339 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
2341 if (!wire_p) glEnable(GL_LIGHTING);
2344 if (tick++ > 10) /* only do this every N frames */
2347 find_mouse_gear (mi);
2353 glCallList (pp->title_list);
2355 if (mi->fps_p) do_fps (mi);
2358 glXSwapBuffers(dpy, window);