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"
47 #include "gltrackball.h"
50 #ifdef USE_GL /* whole file */
55 unsigned long id; /* unique name */
56 double x, y, z; /* position */
57 double r; /* radius of the gear, at middle of teeth */
58 double th; /* rotation (degrees) */
60 GLint nteeth; /* how many teeth */
61 double tooth_w, tooth_h; /* size of teeth */
63 double inner_r; /* radius of the (larger) inside hole */
64 double inner_r2; /* radius of the (smaller) inside hole, if any */
65 double inner_r3; /* yet another */
67 double thickness; /* height of the edge */
68 double thickness2; /* height of the (smaller) inside disc if any */
69 double thickness3; /* yet another */
70 int spokes; /* how many spokes inside, if any */
71 int nubs; /* how many little nubbly bits, if any */
72 double spoke_thickness; /* spoke versus hole */
73 GLfloat wobble; /* factory defect! */
74 int motion_blur_p; /* whether it's spinning too fast to draw */
75 int polygons; /* how many polys in this gear */
77 double ratio; /* gearing ratio with previous gears */
78 double rpm; /* approximate revolutions per minute */
80 Bool base_p; /* whether this gear begins a new train */
81 int coax_p; /* whether this is one of a pair of bound gears.
82 1 for first, 2 for second. */
83 double coax_thickness; /* thickness of the other gear in the pair */
92 GLXContext *glx_context;
93 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
94 GLfloat vp_width, vp_height;
95 GLfloat render_left, render_right; /* area in which gears are displayed */
96 GLfloat layout_left, layout_right; /* layout region, on the right side */
102 trackball_state *trackball;
104 unsigned long mouse_gear_id;
106 XFontStruct *xfont1, *xfont2, *xfont3;
107 GLuint font1_dlist, font2_dlist, font3_dlist;
110 } pinion_configuration;
113 static pinion_configuration *pps = NULL;
114 static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
115 static GLfloat plane_displacement = 0.1; /* distance between coaxial gears */
117 static Bool verbose_p = False; /* print progress on stderr */
118 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
119 static Bool debug_p = False; /* render as flat schematic */
120 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
121 static Bool wire_all_p = False; /* in wireframe, do not abbreviate */
123 static int debug_size_failures; /* for debugging messages */
124 static int debug_position_failures;
125 static unsigned long current_length; /* gear count in current train */
126 static unsigned long current_blur_length; /* how long have we been blurring? */
129 static XrmOptionDescRec opts[] = {
130 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
131 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
132 { "-size", ".gearSize", XrmoptionSepArg, 0 },
133 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
134 { "-debug", ".debug", XrmoptionNoArg, "True" },
135 { "-verbose",".verbose", XrmoptionNoArg, "True" },
138 static argtype vars[] = {
139 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
140 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
141 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
142 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
143 {&debug_p, "debug", "Debug", "False", t_Bool},
144 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
147 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
150 /* Computing normal vectors
157 /* Calculate the unit normal at p given two other points p1,p2 on the
158 surface. The normal points in the direction of p1 crossproduct p2
161 calc_normal (XYZ p, XYZ p1, XYZ p2)
170 n.x = pa.y * pb.z - pa.z * pb.y;
171 n.y = pa.z * pb.x - pa.x * pb.z;
172 n.z = pa.x * pb.y - pa.y * pb.x;
177 do_normal(GLfloat x1, GLfloat y1, GLfloat z1,
178 GLfloat x2, GLfloat y2, GLfloat z2,
179 GLfloat x3, GLfloat y3, GLfloat z3)
182 p1.x = x1; p1.y = y1; p1.z = z1;
183 p2.x = x2; p2.y = y2; p2.z = z2;
184 p3.x = x3; p3.y = y3; p3.z = z3;
185 p = calc_normal (p1, p2, p3);
186 glNormal3f (p.x, p.y, p.z);
194 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
196 const char *font = get_string_resource (res, "Font");
201 if (!font) font = "-*-times-bold-r-normal-*-180-*";
203 f = XLoadQueryFont(mi->dpy, font);
204 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
207 first = f->min_char_or_byte2;
208 last = f->max_char_or_byte2;
211 *dlistP = glGenLists ((GLuint) last+1);
212 check_gl_error ("glGenLists");
213 glXUseXFont(id, first, last-first+1, *dlistP + first);
214 check_gl_error ("glXUseXFont");
221 load_fonts (ModeInfo *mi)
223 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
224 load_font (mi, "titleFont", &pp->xfont1, &pp->font1_dlist);
225 load_font (mi, "titleFont2", &pp->xfont2, &pp->font2_dlist);
226 load_font (mi, "titleFont3", &pp->xfont3, &pp->font3_dlist);
231 string_width (XFontStruct *f, const char *c)
236 int cc = *((unsigned char *) c);
238 ? f->per_char[cc-f->min_char_or_byte2].rbearing
239 : f->min_bounds.rbearing);
246 print_title_string (ModeInfo *mi, const char *string,
247 GLfloat x, GLfloat y,
248 XFontStruct *font, int font_dlist)
250 GLfloat line_height = font->ascent + font->descent;
251 GLfloat sub_shift = (line_height * 0.3);
252 int cw = string_width (font, "m");
257 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
258 GL_ENABLE_BIT); /* for various glDisable calls */
259 glDisable (GL_LIGHTING);
260 glDisable (GL_DEPTH_TEST);
262 glMatrixMode(GL_PROJECTION);
267 glMatrixMode(GL_MODELVIEW);
275 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
277 glColor3f (0.8, 0.8, 0);
279 glRasterPos2f (x, y);
280 for (i = 0; i < strlen(string); i++)
285 glRasterPos2f (x, (y -= line_height));
291 x2 = ((x2 + tabs) / tabs) * tabs; /* tab to tab stop */
293 glRasterPos2f (x2, y);
295 else if (c == '[' && (isdigit (string[i+1])))
298 glRasterPos2f (x2, (y -= sub_shift));
300 else if (c == ']' && sub_p)
303 glRasterPos2f (x2, (y += sub_shift));
307 glCallList (font_dlist + (int)(c));
308 x2 += (font->per_char
309 ? font->per_char[c - font->min_char_or_byte2].width
310 : font->min_bounds.width);
316 glMatrixMode(GL_PROJECTION);
321 glMatrixMode(GL_MODELVIEW);
324 static void rpm_string (double rpm, char *s);
327 new_label (ModeInfo *mi)
329 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
334 if (pp->mouse_gear_id)
335 for (i = 0; i < pp->ngears; i++)
336 if (pp->gears[i]->id == pp->mouse_gear_id)
346 sprintf (label, "%d teeth\n", g->nteeth);
347 rpm_string (g->rpm, label + strlen(label));
350 glNewList (pp->title_list, GL_COMPILE);
355 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
356 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
357 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
358 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
360 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
362 print_title_string (mi, label,
363 10, mi->xgwa.height - 10,
374 /* Find the gear in the scene that is farthest to the right or left.
377 farthest_gear (ModeInfo *mi, Bool left_p)
379 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
382 double x = (left_p ? 999999 : -999999);
383 for (i = 0; i < pp->ngears; i++)
385 gear *g = pp->gears[i];
386 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
387 if (left_p ? (x > gx) : (x < gx))
397 /* Compute the revolutions per minute of a gear.
400 compute_rpm (ModeInfo *mi, gear *g)
402 double fps, rpf, rps;
403 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
405 if (fps > 150) fps = 150; /* let's be reasonable... */
406 if (fps < 10) fps = 10;
408 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
409 rps = rpf * fps; /* rotations per second */
413 /* Prints the RPM into a string, doing fancy float formatting.
416 rpm_string (double rpm, char *s)
420 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
421 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
422 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
423 else sprintf (buf, "%.16f",rpm);
426 while (buf[L-1] == '0') buf[--L] = 0;
427 if (buf[L-1] == '.') buf[--L] = 0;
434 /* Which of the gear's inside rings is the biggest?
437 biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
439 double r0 = (g->r - g->tooth_h/2);
440 double r1 = g->inner_r;
441 double r2 = g->inner_r2;
442 double r3 = g->inner_r3;
443 double w1 = (r1 ? r0 - r1 : r0);
444 double w2 = (r2 ? r1 - r2 : 0);
445 double w3 = (r3 ? r2 - r3 : 0);
446 double h1 = g->thickness;
447 double h2 = g->thickness2;
448 double h3 = g->thickness3;
450 if (g->spokes) w2 = 0;
452 if (w1 > w2 && w1 > w3)
454 if (posP) *posP = (r0+r1)/2;
455 if (sizeP) *sizeP = w1;
456 if (heightP) *heightP = h1;
459 else if (w2 > w1 && w2 > w3)
461 if (posP) *posP = (r1+r2)/2;
462 if (sizeP) *sizeP = w2;
463 if (heightP) *heightP = h2;
468 if (posP) *posP = (r2+r3)/2;
469 if (sizeP) *sizeP = w3;
470 if (heightP) *heightP = h3;
480 /* Create and return a new gear sized for placement next to or on top of
481 the given parent gear (if any.) Returns 0 if out of memory.
484 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
486 /* pinion_configuration *pp = &pps[MI_SCREEN(mi)]; */
487 gear *g = (gear *) calloc (1, sizeof (*g));
489 static unsigned long id = 0;
492 if (coaxial_p && !parent) abort();
498 if (loop_count > 1000)
499 /* The only time we loop in here is when making a coaxial gear, and
500 trying to pick a radius that is either significantly larger or
501 smaller than its parent. That shouldn't be hard, so something
502 must be really wrong if we can't do that in only a few tries.
506 /* Pick the size of the teeth.
508 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
510 g->tooth_w = parent->tooth_w;
511 g->tooth_h = parent->tooth_h;
512 g->thickness = parent->thickness;
513 g->thickness2 = parent->thickness2;
514 g->thickness3 = parent->thickness3;
516 else /* gears that begin trains get any size they want */
518 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
519 g->tooth_w = 0.007 * scale;
520 g->tooth_h = 0.005 * scale;
521 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
522 g->thickness2 = g->thickness / 4;
523 g->thickness3 = g->thickness;
526 /* Pick the number of teeth, and thus, the radius.
532 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
534 if (g->nteeth < 7 && (random() % 5) != 0)
535 goto AGAIN; /* Let's make very small tooth-counts more rare */
537 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
538 g->r = c / (M_PI * 2); /* c = 2 pi r */
544 if (! coaxial_p) break; /* yes */
545 if (g->nteeth == parent->nteeth) continue; /* ugly */
546 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
547 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
552 g->color[0] = 0.5 + frand(0.5);
553 g->color[1] = 0.5 + frand(0.5);
554 g->color[2] = 0.5 + frand(0.5);
557 g->color2[0] = g->color[0] * 0.85;
558 g->color2[1] = g->color[1] * 0.85;
559 g->color2[2] = g->color[2] * 0.85;
560 g->color2[3] = g->color[3];
563 /* Decide on shape of gear interior:
564 - just a ring with teeth;
565 - that, plus a thinner in-set "plate" in the middle;
566 - that, plus a thin raised "lip" on the inner plate;
567 - or, a wide lip (really, a thicker third inner plate.)
569 if ((random() % 10) == 0)
571 /* inner_r can go all the way in; there's no inset disc. */
572 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
578 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
579 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
580 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
583 if (g->inner_r2 > (g->r * 0.2))
585 int nn = (random() % 10);
587 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
588 else if (nn <= 7 && g->inner_r2 >= 0.1)
589 g->inner_r3 = g->inner_r2 - 0.01;
593 /* Coaxial gears need to have the same innermost hole size (for the axle.)
594 Use whichever of the two is smaller. (Modifies parent.)
598 double hole1 = (g->inner_r3 ? g->inner_r3 :
599 g->inner_r2 ? g->inner_r2 :
601 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
602 parent->inner_r2 ? parent->inner_r2 :
604 double hole = (hole1 < hole2 ? hole1 : hole2);
605 if (hole <= 0) abort();
607 if (g->inner_r3) g->inner_r3 = hole;
608 else if (g->inner_r2) g->inner_r2 = hole;
609 else g->inner_r = hole;
611 if (parent->inner_r3) parent->inner_r3 = hole;
612 else if (parent->inner_r2) parent->inner_r2 = hole;
613 else parent->inner_r = hole;
616 /* If we have three discs, sometimes make the middle disc be spokes.
618 if (g->inner_r3 && ((random() % 5) == 0))
620 g->spokes = 2 + BELLRAND (5);
621 g->spoke_thickness = 1 + frand(7.0);
622 if (g->spokes == 2 && g->spoke_thickness < 2)
623 g->spoke_thickness += 1;
626 /* Sometimes add little nubbly bits, if there is room.
631 biggest_ring (g, 0, &size, 0);
632 if (size > g->r * 0.2 && (random() % 5) == 0)
634 g->nubs = 1 + (random() % 16);
635 if (g->nubs > 8) g->nubs = 1;
639 if (g->inner_r3 > g->inner_r2) abort();
640 if (g->inner_r2 > g->inner_r) abort();
641 if (g->inner_r > g->r) abort();
649 /* Given a newly-created gear, place it next to its parent in the scene,
650 with its teeth meshed and the proper velocity. Returns False if it
651 didn't work. (Call this a bunch of times until either it works, or
652 you decide it's probably not going to.)
655 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
657 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
659 /* If this gear takes up more than 1/3rd of the screen, it's no good.
661 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
662 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
664 if (verbose_p && debug_placement_p && 0)
666 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
668 (g->r + g->tooth_h), gear_size,
669 pp->vp_width, pp->vp_height);
670 debug_size_failures++;
674 /* Compute this gear's velocity.
678 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
679 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
683 g->ratio = parent->ratio; /* bound gears have the same ratio */
685 g->rpm = parent->rpm;
686 g->wobble = parent->wobble;
690 /* Gearing ratio is the ratio of the number of teeth to previous gear
691 (which is also the ratio of the circumferences.)
693 g->ratio = (double) parent->nteeth / (double) g->nteeth;
695 /* Set our initial rotation to match that of the previous gear,
696 multiplied by the gearing ratio. (This is finessed later,
697 once we know the exact position of the gear relative to its
700 g->th = -(parent->th * g->ratio);
702 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
704 double off = (180.0 / g->nteeth);
711 /* ratios are cumulative for all gears in the train. */
712 g->ratio *= parent->ratio;
716 /* Place the gear relative to the parent.
721 gear *rg = farthest_gear (mi, False);
722 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
723 if (right < pp->layout_left) /* place off screen */
724 right = pp->layout_left;
726 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
730 if (debug_one_gear_p)
735 double off = plane_displacement;
739 g->z = parent->z + (g->r > parent->r /* small gear on top */
742 if (parent->r > g->r) /* mark which is top and which is bottom */
746 parent->wobble = 0; /* looks bad when axle moves */
755 g->coax_thickness = parent->thickness;
756 parent->coax_thickness = g->thickness;
758 /* Don't let the train get too close to or far from the screen.
759 If we're getting too close, give up on this gear.
760 (But getting very far away is fine.)
762 if (g->z >= off * 4 ||
765 if (verbose_p && debug_placement_p)
766 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
768 debug_position_failures++;
772 else /* position it somewhere next to the parent. */
774 double r_off = parent->r + g->r;
777 if ((random() % 3) != 0)
778 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
780 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
782 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
783 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
786 /* If the angle we picked would have positioned this gear
787 more than halfway off screen, that's no good. */
788 if (g->y > pp->vp_top ||
789 g->y < pp->vp_bottom)
791 if (verbose_p && debug_placement_p)
792 fprintf (stderr, "%s: placement: out of bounds: %s\n",
793 progname, (g->y > pp->vp_top ? "top" : "bottom"));
794 debug_position_failures++;
798 /* avoid accidentally changing sign of "th" in the math below. */
799 g->th += (g->th > 0 ? 360 : -360);
801 /* Adjust the rotation of the gear so that its teeth line up with its
802 parent, based on the position of the gear and the current rotation
806 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
807 double g_c = 2 * M_PI * g->r; /* circumference of g */
809 double p_t = p_c * (angle/360.0); /* distance travelled along
810 circumference of parent when
811 moving "angle" degrees along
813 double g_rat = p_t / g_c; /* if travelling that distance
814 along circumference of g,
815 ratio of g's circumference
817 double g_th = 360.0 * g_rat; /* that ratio in degrees */
819 g->th += angle + g_th;
823 if (debug_one_gear_p)
829 /* If the position we picked for this gear would cause it to already
830 be visible on the screen, give up. This can happen when the train
831 is growing backwards, and we don't want to see gears flash into
834 if (g->x - g->r - g->tooth_h < pp->render_right)
836 if (verbose_p && debug_placement_p)
837 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
838 debug_position_failures++;
842 /* If the position we picked for this gear causes it to overlap
843 with any earlier gear in the train, give up.
848 for (i = pp->ngears-1; i >= 0; i--)
850 gear *og = pp->gears[i];
852 if (og == g) continue;
853 if (og == parent) continue;
854 if (g->z != og->z) continue; /* Ignore unless on same layer */
856 /* Collision detection without sqrt:
857 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
858 d < r1 + r2 d^2 < (r1 + r2)^2
860 if (((g->x - og->x) * (g->x - og->x) +
861 (g->y - og->y) * (g->y - og->y)) <
862 ((g->r + g->tooth_h + og->r + og->tooth_h) *
863 (g->r + g->tooth_h + og->r + og->tooth_h)))
865 if (verbose_p && debug_placement_p)
866 fprintf (stderr, "%s: placement: collision with %lu\n",
868 debug_position_failures++;
877 /* Make deeper gears be darker.
880 double depth = g->z / plane_displacement;
881 double brightness = 1 + (depth / 6);
883 if (brightness < limit) brightness = limit;
884 if (brightness > 1/limit) brightness = 1/limit;
885 g->color[0] *= brightness;
886 g->color[1] *= brightness;
887 g->color[2] *= brightness;
888 g->color2[0] *= brightness;
889 g->color2[1] *= brightness;
890 g->color2[2] *= brightness;
893 /* If a single frame of animation would cause the gear to rotate by
894 more than 1/2 the size of a single tooth, then it won't look right:
895 the gear will appear to be turning at some lower harmonic of its
899 double ratio = g->ratio * spin_speed;
900 double blur_limit = 180.0 / g->nteeth;
902 if (ratio > blur_limit)
903 g->motion_blur_p = 1;
907 /* ride until the wheels fall off... */
908 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
909 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
910 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
911 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
912 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
913 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
924 glDeleteLists (g->dlist, 1);
929 /* Make a new gear, place it next to its parent in the scene,
930 with its teeth meshed and the proper velocity. Returns the gear;
931 or 0 if it didn't work. (Call this a bunch of times until either
932 it works, or you decide it's probably not going to.)
935 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
937 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
944 if (loop_count >= 100)
952 g = new_gear (mi, parent, coaxial_p);
953 if (!g) return 0; /* out of memory? */
955 if (place_gear (mi, g, parent, coaxial_p))
961 /* We got a gear, and it is properly positioned.
962 Insert it in the scene.
964 if (pp->ngears + 2 >= pp->gears_size)
966 pp->gears_size += 100;
967 pp->gears = (gear **) realloc (pp->gears,
968 pp->gears_size * sizeof (*pp->gears));
971 fprintf (stderr, "%s: out of memory (%d gears)\n",
972 progname, pp->gears_size);
976 pp->gears[pp->ngears++] = g;
981 static void delete_gear (ModeInfo *mi, gear *g);
984 push_gear (ModeInfo *mi)
986 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
988 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
990 Bool tried_coaxial_p = False;
991 Bool coaxial_p = False;
992 Bool last_ditch_coax_p = False;
995 debug_size_failures = 0;
996 debug_position_failures = 0;
1000 if (loop_count > 100) abort(); /* we're doomed! */
1004 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
1005 reset things to a sane velocity.
1007 10,000 RPM at 30 FPS = 5.5 rotations per frame.
1008 1,000 RPM at 30 FPS = 0.5 rotations per frame.
1009 600 RPM at 30 FPS = 3 frames per rotation.
1010 200 RPM at 30 FPS = 9 frames per rotation.
1011 100 RPM at 30 FPS = 18 frames per rotation.
1012 50 RPM at 30 FPS = 36 frames per rotation.
1013 10 RPM at 30 FPS = 3 sec per rotation.
1014 1 RPM at 30 FPS = 30 sec per rotation.
1015 .5 RPM at 30 FPS = 1 min per rotation.
1016 .1 RPM at 30 FPS = 5 min per rotation.
1018 if (parent && parent->rpm > max_rpm)
1023 rpm_string (parent->rpm, buf);
1024 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
1029 /* If the last N gears we've placed have all been motion-blurred, then
1030 it's a safe guess that we've wandered off into the woods and aren't
1031 coming back. Bail on this train.
1033 if (current_blur_length >= 10)
1036 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
1042 /* Sometimes, try to make a coaxial gear.
1044 if (parent && !parent->coax_p && (random() % 40) == 0)
1046 tried_coaxial_p = True;
1048 g = place_new_gear (mi, parent, coaxial_p);
1051 /* Try to make a regular gear.
1056 g = place_new_gear (mi, parent, coaxial_p);
1059 /* If we couldn't make a regular gear, then try to make a coxial gear
1060 (unless we already tried that.)
1062 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
1064 tried_coaxial_p = True;
1066 g = place_new_gear (mi, parent, coaxial_p);
1068 last_ditch_coax_p = True;
1071 /* If we couldn't do that either, then the train has hit a dead end:
1078 fprintf (stderr, "%s: dead end!\n\n", progname);
1080 g = place_new_gear (mi, parent, coaxial_p);
1085 /* Unable to make/place any gears at all!
1086 This can happen if we've backed ourself into a corner very near
1087 the top-right or bottom-right corner of the growth zone.
1088 It's time to add a gear, but there's no room to add one!
1089 In that case, let's just wipe all the gears that are in the
1090 growth zone and try again.
1094 if (verbose_p && debug_placement_p)
1096 "%s: placement: resetting growth zone! "
1097 "failed: %d size, %d pos\n",
1099 debug_size_failures, debug_position_failures);
1100 for (i = pp->ngears-1; i >= 0; i--)
1102 gear *g = pp->gears[i];
1103 if (g->x - g->r - g->tooth_h < pp->render_left)
1104 delete_gear (mi, g);
1111 if (g->x != parent->x) abort();
1112 if (g->y != parent->y) abort();
1113 if (g->z == parent->z) abort();
1114 if (g->r == parent->r) abort();
1115 if (g->th != parent->th) abort();
1116 if (g->ratio != parent->ratio) abort();
1117 if (g->rpm != parent->rpm) abort();
1122 fprintf (stderr, "%s: %5lu ", progname, g->id);
1124 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
1125 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
1126 g->coax_p ? '1' : ' '),
1129 fprintf (stderr, " %2d%%",
1130 (int) (g->r * 2 * 100 / pp->vp_height));
1131 fprintf (stderr, " %2d teeth", g->nteeth);
1132 fprintf (stderr, " %3.0f rpm;", g->rpm);
1135 char buf1[50], buf2[50], buf3[100];
1136 *buf1 = 0; *buf2 = 0; *buf3 = 0;
1137 if (debug_size_failures)
1138 sprintf (buf1, "%3d sz", debug_size_failures);
1139 if (debug_position_failures)
1140 sprintf (buf2, "%2d pos", debug_position_failures);
1142 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
1143 fprintf (stderr, "%-21s", buf3);
1146 if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
1147 fprintf (stderr, "\n");
1155 if (g->motion_blur_p)
1156 current_blur_length++;
1158 current_blur_length = 0;
1163 /* Remove the given gear from the scene and free it.
1166 delete_gear (ModeInfo *mi, gear *g)
1168 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1171 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
1172 if (pp->gears[i] == g) break;
1173 if (pp->gears[i] != g) abort();
1175 for (; i < pp->ngears-1; i++) /* pull later entries forward */
1176 pp->gears[i] = pp->gears[i+1];
1183 /* Update the position of each gear in the scene.
1186 scroll_gears (ModeInfo *mi)
1188 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1191 for (i = 0; i < pp->ngears; i++)
1192 pp->gears[i]->x -= (scroll_speed * 0.002);
1194 /* if the right edge of any gear is off screen to the left, delete it.
1196 for (i = pp->ngears-1; i >= 0; i--)
1198 gear *g = pp->gears[i];
1199 if (g->x + g->r + g->tooth_h < pp->render_left)
1200 delete_gear (mi, g);
1203 /* if the right edge of the last-added gear is left of the right edge
1204 of the layout area, add another gear.
1209 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1210 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1218 if (i > 1 && verbose_p)
1219 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1224 /* Update the rotation of each gear in the scene.
1227 spin_gears (ModeInfo *mi)
1229 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1232 for (i = 0; i < pp->ngears; i++)
1234 gear *g = pp->gears[i];
1235 double off = (g->ratio * spin_speed);
1245 /* Run the animation fast (without displaying anything) until the first
1246 gear is just about to come on screen. This is to avoid a big delay
1247 with a blank screen when -scroll is low.
1252 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1255 gear *g = farthest_gear (mi, True);
1256 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1264 /* Rendering the 3D objects into the scene.
1268 /* Draws an uncapped tube of the given radius extending from top to bottom,
1269 with faces pointing either in or out.
1272 draw_ring (ModeInfo *mi, int segments,
1273 GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1277 Bool wire_p = MI_IS_WIREFRAME(mi);
1278 GLfloat width = M_PI * 2 / segments;
1282 glFrontFace (in_p ? GL_CCW : GL_CW);
1283 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1284 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1286 GLfloat th = i * width;
1287 GLfloat cth = cos(th);
1288 GLfloat sth = sin(th);
1290 glNormal3f (-cth, -sth, 0);
1292 glNormal3f (cth, sth, 0);
1293 glVertex3f (cth * r, sth * r, top);
1294 glVertex3f (cth * r, sth * r, bottom);
1302 glBegin (GL_LINE_LOOP);
1303 for (i = 0; i < segments; i++)
1305 GLfloat th = i * width;
1306 glVertex3f (cos(th) * r, sin(th) * r, top);
1309 glBegin (GL_LINE_LOOP);
1310 for (i = 0; i < segments; i++)
1312 GLfloat th = i * width;
1313 glVertex3f (cos(th) * r, sin(th) * r, bottom);
1322 /* Draws a donut-shaped disc between the given radii,
1323 with faces pointing either up or down.
1324 The first radius may be 0, in which case, a filled disc is drawn.
1327 draw_disc (ModeInfo *mi, int segments,
1328 GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1332 Bool wire_p = MI_IS_WIREFRAME(mi);
1333 GLfloat width = M_PI * 2 / segments;
1335 if (ra < 0) abort();
1336 if (rb <= 0) abort();
1339 glFrontFace (up_p ? GL_CW : GL_CCW);
1341 glFrontFace (up_p ? GL_CCW : GL_CW);
1344 glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1346 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1348 glNormal3f (0, 0, (up_p ? -1 : 1));
1350 if (ra == 0 && !wire_p)
1351 glVertex3f (0, 0, z);
1353 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1355 GLfloat th = i * width;
1356 GLfloat cth = cos(th);
1357 GLfloat sth = sin(th);
1358 if (wire_p || ra != 0)
1359 glVertex3f (cth * ra, sth * ra, z);
1360 glVertex3f (cth * rb, sth * rb, z);
1368 /* Draws N thick radial lines between the given radii,
1369 with faces pointing either up or down.
1372 draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1373 GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1377 Bool wire_p = MI_IS_WIREFRAME(mi);
1380 int insegs, outsegs;
1384 if (ra <= 0 || rb <= 0) abort();
1388 while (segments2 < segments) /* need a multiple of N >= segments */
1389 segments2 += n; /* (yes, this is a moronic way to find that) */
1391 insegs = ((float) (segments2 / n) + 0.5) / thickness;
1392 outsegs = (segments2 / n) - insegs;
1393 if (insegs <= 0) insegs = 1;
1394 if (outsegs <= 0) outsegs = 1;
1396 segments2 = (insegs + outsegs) * n;
1397 width = M_PI * 2 / segments2;
1401 for (i = 0; i < segments2; i++, tick++)
1403 GLfloat th1 = i * width;
1404 GLfloat th2 = th1 + width;
1405 GLfloat cth1 = cos(th1);
1406 GLfloat sth1 = sin(th1);
1407 GLfloat cth2 = cos(th2);
1408 GLfloat sth2 = sin(th2);
1411 int changed = (i == 0);
1413 if (state == 0 && tick == insegs)
1414 tick = 0, state = 1, changed = 1;
1415 else if (state == 1 && tick == outsegs)
1416 tick = 0, state = 0, changed = 1;
1418 if ((state == 1 || /* in */
1419 (state == 0 && changed)) &&
1420 (!wire_p || wire_all_p))
1423 glFrontFace (GL_CCW);
1424 glBegin (wire_p ? GL_LINES : GL_QUADS);
1425 glNormal3f (0, 0, -1);
1426 glVertex3f (cth1 * ra, sth1 * ra, z1);
1427 glVertex3f (cth1 * rb, sth1 * rb, z1);
1428 glVertex3f (cth2 * rb, sth2 * rb, z1);
1429 glVertex3f (cth2 * ra, sth2 * ra, z1);
1434 glFrontFace (GL_CW);
1435 glBegin (wire_p ? GL_LINES : GL_QUADS);
1436 glNormal3f (0, 0, 1);
1437 glVertex3f (cth1 * ra, sth1 * ra, z2);
1438 glVertex3f (cth1 * rb, sth1 * rb, z2);
1439 glVertex3f (cth2 * rb, sth2 * rb, z2);
1440 glVertex3f (cth2 * ra, sth2 * ra, z2);
1445 if (state == 1 && changed) /* entering "in" state */
1448 glFrontFace (GL_CW);
1449 glBegin (wire_p ? GL_LINES : GL_QUADS);
1450 do_normal (cth1 * rb, sth1 * rb, z1,
1451 cth1 * ra, sth1 * ra, z1,
1452 cth1 * rb, sth1 * rb, z2);
1453 glVertex3f (cth1 * ra, sth1 * ra, z1);
1454 glVertex3f (cth1 * rb, sth1 * rb, z1);
1455 glVertex3f (cth1 * rb, sth1 * rb, z2);
1456 glVertex3f (cth1 * ra, sth1 * ra, z2);
1461 if (state == 0 && changed) /* entering "out" state */
1464 glFrontFace (GL_CCW);
1465 glBegin (wire_p ? GL_LINES : GL_QUADS);
1466 do_normal (cth2 * ra, sth2 * ra, z1,
1467 cth2 * rb, sth2 * rb, z1,
1468 cth2 * rb, sth2 * rb, z2);
1469 glVertex3f (cth2 * ra, sth2 * ra, z1);
1470 glVertex3f (cth2 * rb, sth2 * rb, z1);
1471 glVertex3f (cth2 * rb, sth2 * rb, z2);
1472 glVertex3f (cth2 * ra, sth2 * ra, z2);
1484 /* Draws some bumps (embedded cylinders) on the gear.
1487 draw_gear_nubs (ModeInfo *mi, gear *g)
1489 Bool wire_p = MI_IS_WIREFRAME(mi);
1493 double r, size, height;
1498 if (! g->nubs) return 0;
1500 which = biggest_ring (g, &r, &size, &height);
1504 cc = (which == 1 ? g->color : g->color2);
1505 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1507 width = M_PI * 2 / g->nubs;
1508 off = M_PI / (g->nteeth * 2); /* align first nub with a tooth */
1510 for (i = 0; i < g->nubs; i++)
1512 GLfloat th = (i * width) + off;
1514 glTranslatef (cos(th) * r, sin(th) * r, 0);
1516 if (wire_p && !wire_all_p)
1517 polys += draw_ring (mi, steps/2, size, 0, 0, False);
1520 polys += draw_disc (mi, steps, 0, size, -height, True);
1521 polys += draw_disc (mi, steps, 0, size, height, False);
1522 polys += draw_ring (mi, steps, size, -height, height, False);
1531 /* Draws a much simpler representation of a gear.
1534 draw_gear_schematic (ModeInfo *mi, gear *g)
1536 Bool wire_p = MI_IS_WIREFRAME(mi);
1539 GLfloat width = M_PI * 2 / g->nteeth;
1541 if (!wire_p) glDisable(GL_LIGHTING);
1542 glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1545 for (i = 0; i < g->nteeth; i++)
1547 GLfloat th = (i * width) + (width/4);
1548 glVertex3f (0, 0, -g->thickness/2);
1549 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1554 glBegin (GL_LINE_LOOP);
1555 for (i = 0; i < g->nteeth; i++)
1557 GLfloat th = (i * width) + (width/4);
1558 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1563 if (!wire_p) glEnable(GL_LIGHTING);
1568 /* Renders all the interior (non-toothy) parts of a gear:
1569 the discs, axles, etc.
1572 draw_gear_interior (ModeInfo *mi, gear *g)
1574 Bool wire_p = MI_IS_WIREFRAME(mi);
1577 int steps = g->nteeth * 2;
1578 if (steps < 10) steps = 10;
1579 if (wire_p && !wire_all_p) steps /= 2;
1581 /* ring 1 (facing in) is done in draw_gear_teeth */
1583 /* ring 2 (facing in) and disc 2
1587 GLfloat ra = g->inner_r;
1588 GLfloat rb = g->inner_r2;
1589 GLfloat za = -g->thickness2/2;
1590 GLfloat zb = g->thickness2/2;
1592 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1594 if ((g->coax_p != 1 && !g->inner_r3) ||
1595 (wire_p && wire_all_p))
1596 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1598 if (wire_p && wire_all_p)
1599 polys += draw_ring (mi, steps, ra, za, zb, True); /* ring facing in */
1602 polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1603 steps, ra, rb, za, zb);
1604 else if (!wire_p || wire_all_p)
1606 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1607 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1611 /* ring 3 (facing in and out) and disc 3
1615 GLfloat ra = g->inner_r2;
1616 GLfloat rb = g->inner_r3;
1617 GLfloat za = -g->thickness3/2;
1618 GLfloat zb = g->thickness3/2;
1620 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1622 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1624 if (g->coax_p != 1 || (wire_p && wire_all_p))
1625 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1627 if (!wire_p || wire_all_p)
1629 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1630 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1638 GLfloat cap_height = g->coax_thickness/3;
1640 GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1641 g->inner_r2 ? g->inner_r2 :
1643 GLfloat za = -(g->thickness/2 + cap_height);
1644 GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
1646 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1648 if (wire_p && !wire_all_p) steps /= 2;
1650 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1652 if (!wire_p || wire_all_p)
1654 polys += draw_disc (mi, steps, 0, ra, za, True); /* top plate */
1655 polys += draw_disc (mi, steps, 0, ra, zb, False); /* bottom plate */
1662 /* Computes the vertices and normals of the teeth of a gear.
1663 This is the heavy lifting: there are a ton of polygons around the
1664 perimiter of a gear, and the normals are difficult (not radial
1667 It would be nice if we could cache this, but the numbers are
1668 different for essentially every gear:
1670 - Every gear has a different inner_r, so the vertices of the
1671 inner ring (and thus, the triangle fans on the top and bottom
1672 faces) are different in a non-scalable way.
1674 - If the ratio between tooth_w and tooth_h changes, the normals
1675 on the outside edges of the teeth are invalid (this can happen
1676 every time we start a new train.)
1678 So instead, we rely on OpenGL display lists to do the cacheing for
1679 us -- we only compute all these normals once per gear, instead of
1680 once per gear per frame.
1683 gear_teeth_geometry (ModeInfo *mi, gear *g,
1684 int *points_per_tooth_ret,
1690 int ppt = 15; /* points per tooth */
1692 GLfloat width = M_PI * 2 / g->nteeth;
1694 GLfloat rh = g->tooth_h;
1696 GLfloat fudge = (g->nteeth >= 5 ? 0 : 0.04); /* reshape small ones a bit */
1698 XYZ *points = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1699 XYZ *fnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1700 XYZ *pnormals = (XYZ *) calloc (ppt * g->nteeth + 1, sizeof(*points));
1703 /* Approximate shape of an "involute" gear tooth.
1706 th0 th1 th2 th3 th4 th5 th6 th7 th8 th9 th10
1707 : : : : : : : : : : :
1708 : : : : : : : : : : :
1709 r0 ........:..:..:...___________...:..:..:......:......:..
1710 : : : /: : :\ : : : : :
1711 : : : / : : : \ : : : : :
1712 : : :/ : : : \: : : : :
1713 r1 ........:.....@...:....:....:...@..:..:......:......:..
1714 : : @: : : : :@ : : : :
1715 (R) ...........:...@.:...:....:....:...:.@..........:......:......
1716 : :@ : : : : : @: : : :
1717 r2 ........:..@..:...:....:....:...:..@:........:......:..
1718 : /: : : : : : :\ : : :
1719 :/ : : : : : : : \: : : /
1720 r3 ......__/..:..:...:....:....:...:..:..\______________/
1721 : : : : : : : : : : :
1722 | : : : : : : : | : :
1723 : : : : : : : : : : :
1724 | : : : : : : : | : :
1725 r4 ......__:_____________________________:________________
1733 r[0] = R + (rh * 0.5);
1734 r[1] = R + (rh * 0.25);
1735 r[2] = R - (rh * 0.25);
1736 r[3] = R - (rh * 0.5);
1741 th[2] = -tw *(0.16 - fudge);
1745 th[6] = tw *(0.16 - fudge);
1749 th[10]= th[0] + width;
1751 if (!points || !fnormals || !pnormals)
1753 fprintf (stderr, "%s: out of memory\n", progname);
1759 /* First, compute the coordinates of every point used for every tooth.
1761 for (i = 0; i < g->nteeth; i++)
1763 GLfloat TH = (i * width) + (width/4);
1766 # define PUSH(PR,PTH) \
1767 points[npoints].x = cos(TH+th[(PTH)]) * r[(PR)]; \
1768 points[npoints].y = sin(TH+th[(PTH)]) * r[(PR)]; \
1771 /* start1 = npoints; */
1773 PUSH(3, 0); /* tooth left 1 */
1774 PUSH(2, 1); /* tooth left 2 */
1775 PUSH(1, 2); /* tooth left 3 */
1776 PUSH(0, 3); /* tooth top 1 */
1777 PUSH(0, 5); /* tooth top 2 */
1778 PUSH(1, 6); /* tooth right 1 */
1779 PUSH(2, 7); /* tooth right 2 */
1780 PUSH(3, 8); /* tooth right 3 */
1781 PUSH(3, 10); /* gap top */
1783 /* end1 = npoints; */
1785 PUSH(4, 8); /* gap interior */
1787 /* start2 = npoints; */
1789 PUSH(4, 10); /* tooth interior 1 */
1790 PUSH(4, 8); /* tooth interior 2 */
1791 PUSH(4, 4); /* tooth bottom 1 */
1792 PUSH(4, 0); /* tooth bottom 2 */
1794 /* end2 = npoints; */
1796 PUSH(3, 4); /* midpoint */
1798 /* mid = npoints-1; */
1800 if (i == 0 && npoints != ppt) abort(); /* go update "ppt"! */
1805 /* Now compute the face normals for each facet on the tooth rim.
1807 for (i = 0; i < npoints; i++)
1814 fnormals[i] = calc_normal (p1, p2, p3);
1818 /* From the face normals, compute the vertex normals (by averaging
1819 the normals of adjascent faces.)
1821 for (i = 0; i < npoints; i++)
1823 int a = (i == 0 ? npoints-1 : i-1);
1826 /* Kludge to fix the normal on the last top point: since the
1827 faces go all the way around, this normal pointed clockwise
1828 instead of radially out. */
1829 int start1 = (i / ppt) * ppt;
1830 int end1 = start1 + 9;
1834 b = (start1 + ppt == npoints ? 0 : start1 + ppt);
1836 n1 = fnormals[a]; /* normal of [i-1 - i] face */
1837 n2 = fnormals[b]; /* normal of [i - i+1] face */
1838 pnormals[i].x = (n1.x + n2.x) / 2;
1839 pnormals[i].y = (n1.y + n2.y) / 2;
1840 pnormals[i].z = (n1.z + n2.z) / 2;
1846 *points_ret = points;
1851 *normals_ret = pnormals;
1855 if (points_per_tooth_ret)
1856 *points_per_tooth_ret = ppt;
1860 /* Renders all teeth of a gear.
1863 draw_gear_teeth (ModeInfo *mi, gear *g)
1865 Bool wire_p = MI_IS_WIREFRAME(mi);
1869 GLfloat z1 = -g->thickness/2;
1870 GLfloat z2 = g->thickness/2;
1873 XYZ *points, *pnormals;
1875 gear_teeth_geometry (mi, g, &ppt, &points, &pnormals);
1877 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1879 for (i = 0; i < g->nteeth; i++)
1884 int start1 = i * ppt;
1885 int end1 = start1 + 9;
1886 int start2 = end1 + 1;
1887 int end2 = start2 + 4;
1890 /* Outside rim of the tooth
1892 glFrontFace (GL_CW);
1893 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1894 for (j = start1; j < end1; j++)
1896 glNormal3f (pnormals[j].x, pnormals[j].y, pnormals[j].z);
1897 glVertex3f (points[j].x, points[j].y, z1);
1898 glVertex3f (points[j].x, points[j].y, z2);
1902 /* Show the face normal vectors */
1905 XYZ n = fnormals[j];
1906 GLfloat x = (points[j].x + points[j+1].x) / 2;
1907 GLfloat y = (points[j].y + points[j+1].y) / 2;
1908 GLfloat z = (z1 + z2) / 2;
1909 glVertex3f (x, y, z);
1910 glVertex3f (x + n.x, y + n.y, z);
1913 /* Show the vertex normal vectors */
1916 XYZ n = pnormals[j];
1917 GLfloat x = points[j].x;
1918 GLfloat y = points[j].y;
1919 GLfloat z = (z1 + z2) / 2;
1920 glVertex3f (x, y, z);
1921 glVertex3f (x + n.x, y + n.y, z);
1927 /* Some more lines for the outside rim of the tooth...
1931 glBegin (GL_LINE_STRIP);
1932 for (j = start1; j < end1; j++)
1933 glVertex3f (points[j].x, points[j].y, z1);
1935 glBegin (GL_LINE_STRIP);
1936 for (j = start1; j < end1; j++)
1937 glVertex3f (points[j].x, points[j].y, z2);
1941 /* Inside rim behind the tooth
1943 glFrontFace (GL_CW);
1944 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1945 for (j = start2; j < end2; j++)
1947 glNormal3f (-points[j].x, -points[j].y, 0);
1948 glVertex3f ( points[j].x, points[j].y, z1);
1949 glVertex3f ( points[j].x, points[j].y, z2);
1954 /* Some more lines for the inside rim...
1958 glBegin (GL_LINE_STRIP);
1959 for (j = start2; j < end2; j++)
1960 glVertex3f (points[j].x, points[j].y, z1);
1962 glBegin (GL_LINE_STRIP);
1963 for (j = start2; j < end2; j++)
1964 glVertex3f (points[j].x, points[j].y, z2);
1968 /* All top and bottom facets. We can skip all of these in wire mode.
1970 if (!wire_p || wire_all_p)
1971 for (z = z1; z <= z2; z += z2-z1)
1973 /* Flat edge of the tooth
1975 glFrontFace (z == z1 ? GL_CW : GL_CCW);
1976 glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1977 glNormal3f (0, 0, z);
1978 for (j = start1; j < end2; j++)
1980 if (j == end1-1 || j == end1 || j == start2)
1981 continue; /* kludge... skip these... */
1983 if (wire_p || j == start1)
1984 glVertex3f (points[mid].x, points[mid].y, z);
1985 glVertex3f (points[j].x, points[j].y, z);
1988 glVertex3f (points[start1].x, points[start1].y, z);
1991 /* Flat edge between teeth
1993 glFrontFace (z == z1 ? GL_CW : GL_CCW);
1994 glBegin (wire_p ? GL_LINES : GL_QUADS);
1995 glNormal3f (0, 0, z);
1996 glVertex3f (points[end1-1 ].x, points[end1-1 ].y, z);
1997 glVertex3f (points[start2 ].x, points[start2 ].y, z);
1998 glVertex3f (points[start2+1].x, points[start2+1].y, z);
1999 glVertex3f (points[end1-2 ].x, points[end1-2 ].y, z);
2011 /* Render one gear, unrotated at 0,0.
2014 draw_gear_1 (ModeInfo *mi, gear *g)
2016 Bool wire_p = MI_IS_WIREFRAME(mi);
2019 static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
2020 static GLfloat shiny = 128.0;
2022 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
2023 glMateriali (GL_FRONT, GL_SHININESS, shiny);
2024 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
2025 glColor3f (g->color[0], g->color[1], g->color[2]);
2027 if (debug_p && wire_p)
2028 polys += draw_gear_schematic (mi, g);
2032 glRotatef (g->wobble, 1, 0, 0);
2033 polys += draw_gear_teeth (mi, g);
2034 polys += draw_gear_interior (mi, g);
2035 polys += draw_gear_nubs (mi, g);
2042 /* Render one gear in the proper position, creating the gear's
2043 display list first if necessary.
2046 draw_gear (ModeInfo *mi, int which)
2048 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2049 gear *g = pp->gears[which];
2052 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
2053 g->x - g->r - g->tooth_h <= pp->render_right);
2055 if (!visible_p && !debug_p)
2060 g->dlist = glGenLists (1);
2063 /* I don't know how many display lists a GL implementation
2064 is supposed to provide, but hopefully it's more than
2065 "a few hundred", or we'll be in trouble...
2067 check_gl_error ("glGenLists");
2071 glNewList (g->dlist, GL_COMPILE);
2072 g->polygons = draw_gear_1 (mi, g);
2078 glTranslatef (g->x, g->y, g->z);
2080 if (g->motion_blur_p && !pp->button_down_p)
2082 /* If we're in motion-blur mode, then draw the gear so that each
2083 frame rotates it by exactly one half tooth-width, so that it
2084 looks flickery around the edges. But, revert to the normal
2085 way when the mouse button is down lest the user see overlapping
2088 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
2094 glRotatef (th, 0, 0, 1);
2099 mi->polygon_count += draw_gear_schematic (mi, g);
2102 glCallList (g->dlist);
2103 mi->polygon_count += g->polygons;
2111 /* Render all gears.
2114 draw_gears (ModeInfo *mi)
2116 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2117 Bool wire_p = MI_IS_WIREFRAME(mi);
2120 glColor4f (1, 1, 0.8, 1);
2124 for (i = 0; i < pp->ngears; i++)
2127 /* draw a line connecting gears that are, uh, geared. */
2130 static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2132 GLfloat ox=0, oy=0, oz=0;
2134 if (!wire_p) glDisable(GL_LIGHTING);
2135 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2136 glColor3f (color[0], color[1], color[2]);
2138 for (i = 0; i < pp->ngears; i++)
2140 gear *g = pp->gears[i];
2141 glBegin(GL_LINE_STRIP);
2142 glVertex3f (g->x, g->y, g->z - off);
2143 glVertex3f (g->x, g->y, g->z + off);
2144 if (i > 0 && !g->base_p)
2145 glVertex3f (ox, oy, oz + off);
2151 if (!wire_p) glEnable(GL_LIGHTING);
2156 /* Mouse hit detection
2159 find_mouse_gear (ModeInfo *mi)
2161 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2162 int screen_width = MI_WIDTH (mi);
2163 int screen_height = MI_HEIGHT (mi);
2164 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2168 pp->mouse_gear_id = 0;
2170 /* Poll mouse position */
2175 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2176 &r, &c, &rx, &ry, &x, &y, &m);
2179 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2180 return; /* out of window */
2182 /* Run OpenGL hit detector */
2187 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
2188 glRenderMode (GL_SELECT);
2189 glMatrixMode (GL_PROJECTION);
2192 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
2193 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2194 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
2195 glMatrixMode (GL_MODELVIEW);
2197 draw_gears (mi); /* render into "select" buffer */
2199 glMatrixMode (GL_PROJECTION); /* restore old vp */
2201 glMatrixMode (GL_MODELVIEW);
2203 hits = glRenderMode (GL_RENDER); /* done selecting */
2208 GLuint name_count = 0;
2209 GLuint *p = (GLuint *) selbuf;
2213 for (i = 0; i < hits; i++)
2216 if (*p < min_z) /* find match closest to screen */
2225 if (name_count > 0) /* take first hit */
2226 pp->mouse_gear_id = pnames[0];
2232 /* Window management, etc
2235 reshape_pinion (ModeInfo *mi, int width, int height)
2237 GLfloat h = (GLfloat) height / (GLfloat) width;
2238 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2240 glViewport (0, 0, (GLint) width, (GLint) height);
2242 glMatrixMode(GL_PROJECTION);
2244 gluPerspective (30.0, 1/h, 1.0, 100.0);
2246 glMatrixMode(GL_MODELVIEW);
2248 gluLookAt( 0.0, 0.0, 30.0,
2252 glClear(GL_COLOR_BUFFER_BIT);
2255 GLfloat render_width, layout_width;
2256 pp->vp_height = 1.0;
2259 pp->vp_left = -pp->vp_width/2;
2260 pp->vp_right = pp->vp_width/2;
2261 pp->vp_top = pp->vp_height/2;
2262 pp->vp_bottom = -pp->vp_height/2;
2264 render_width = pp->vp_width * 2;
2265 layout_width = pp->vp_width * 0.8 * gear_size;
2267 pp->render_left = -render_width/2;
2268 pp->render_right = render_width/2;
2270 pp->layout_left = pp->render_right;
2271 pp->layout_right = pp->layout_left + layout_width;
2277 pinion_handle_event (ModeInfo *mi, XEvent *event)
2279 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2281 if (event->xany.type == ButtonPress &&
2282 event->xbutton.button & Button1)
2284 pp->button_down_p = True;
2285 gltrackball_start (pp->trackball,
2286 event->xbutton.x, event->xbutton.y,
2287 MI_WIDTH (mi), MI_HEIGHT (mi));
2290 else if (event->xany.type == ButtonRelease &&
2291 event->xbutton.button & Button1)
2293 pp->button_down_p = False;
2296 else if (event->xany.type == MotionNotify &&
2299 gltrackball_track (pp->trackball,
2300 event->xmotion.x, event->xmotion.y,
2301 MI_WIDTH (mi), MI_HEIGHT (mi));
2304 else if (event->xany.type == KeyPress)
2308 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2309 if (c == ' ' && debug_one_gear_p && pp->ngears)
2311 delete_gear (mi, pp->gears[0]);
2321 init_pinion (ModeInfo *mi)
2323 pinion_configuration *pp;
2324 int wire = MI_IS_WIREFRAME(mi);
2327 pps = (pinion_configuration *)
2328 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2330 fprintf(stderr, "%s: out of memory\n", progname);
2334 pp = &pps[MI_SCREEN(mi)];
2337 pp = &pps[MI_SCREEN(mi)];
2339 pp->glx_context = init_GL(mi);
2342 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2344 pp->title_list = glGenLists (1);
2350 plane_displacement *= gear_size;
2354 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2355 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2356 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2357 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2359 glEnable(GL_LIGHTING);
2360 glEnable(GL_LIGHT0);
2361 glEnable(GL_DEPTH_TEST);
2362 glEnable(GL_CULL_FACE);
2364 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2365 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2366 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2367 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2370 pp->trackball = gltrackball_init ();
2377 draw_pinion (ModeInfo *mi)
2379 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2380 Display *dpy = MI_DISPLAY(mi);
2381 Window window = MI_WINDOW(mi);
2382 Bool wire_p = MI_IS_WIREFRAME(mi);
2383 static int tick = 0;
2385 if (!pp->glx_context)
2388 if (!pp->button_down_p)
2390 if (!debug_one_gear_p || pp->ngears == 0)
2395 glShadeModel(GL_SMOOTH);
2397 glEnable(GL_DEPTH_TEST);
2398 glEnable(GL_NORMALIZE);
2399 glEnable(GL_CULL_FACE);
2401 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2405 gltrackball_rotate (pp->trackball);
2406 mi->polygon_count = 0;
2408 glScalef (16, 16, 16); /* map vp_width/height to the screen */
2410 if (debug_one_gear_p) /* zoom in */
2412 else if (debug_p) /* show the "visible" and "layout" areas */
2414 GLfloat ow = pp->layout_right - pp->render_left;
2415 GLfloat rw = pp->render_right - pp->render_left;
2416 GLfloat s = (pp->vp_width / ow) * 0.85;
2418 glTranslatef (-(ow - rw) / 2, 0, 0);
2423 glScalef (s, s, s); /* zoom in a little more */
2424 glRotatef (-35, 1, 0, 0); /* tilt back */
2425 glRotatef ( 8, 0, 1, 0); /* tilt left */
2426 glTranslatef (0.02, 0.1, 0); /* pan up */
2433 if (!wire_p) glDisable(GL_LIGHTING);
2434 glColor3f (0.6, 0, 0);
2435 glBegin(GL_LINE_LOOP);
2436 glVertex3f (pp->render_left, pp->vp_top, 0);
2437 glVertex3f (pp->render_right, pp->vp_top, 0);
2438 glVertex3f (pp->render_right, pp->vp_bottom, 0);
2439 glVertex3f (pp->render_left, pp->vp_bottom, 0);
2441 glColor3f (0.4, 0, 0);
2443 glVertex3f (pp->vp_left, pp->vp_top, 0);
2444 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
2445 glVertex3f (pp->vp_right, pp->vp_top, 0);
2446 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
2448 glColor3f (0, 0.4, 0);
2449 glBegin(GL_LINE_LOOP);
2450 glVertex3f (pp->layout_left, pp->vp_top, 0);
2451 glVertex3f (pp->layout_right, pp->vp_top, 0);
2452 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2453 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
2455 if (!wire_p) glEnable(GL_LIGHTING);
2458 if (tick++ > 10) /* only do this every N frames */
2461 find_mouse_gear (mi);
2467 glCallList (pp->title_list);
2469 if (mi->fps_p) do_fps (mi);
2472 glXSwapBuffers(dpy, window);