1 /* pinion, Copyright (c) 2004-2006 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 #define DEFAULTS "*delay: 15000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
15 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
16 "*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
17 "*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
19 # define refresh_pinion 0
20 # define release_pinion 0
22 #define countof(x) (sizeof((x))/sizeof((*x)))
25 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
27 #include "xlockmore.h"
29 #include "gltrackball.h"
33 #ifdef USE_GL /* whole file */
35 #define DEF_SPIN_SPEED "1.0"
36 #define DEF_SCROLL_SPEED "1.0"
37 #define DEF_GEAR_SIZE "1.0"
38 #define DEF_MAX_RPM "900"
41 unsigned long id; /* unique name */
42 double x, y, z; /* position */
43 double r; /* radius of the gear, at middle of teeth */
44 double th; /* rotation (degrees) */
46 GLint nteeth; /* how many teeth */
47 double tooth_w, tooth_h; /* size of teeth */
49 double inner_r; /* radius of the (larger) inside hole */
50 double inner_r2; /* radius of the (smaller) inside hole, if any */
51 double inner_r3; /* yet another */
53 double thickness; /* height of the edge */
54 double thickness2; /* height of the (smaller) inside disc if any */
55 double thickness3; /* yet another */
56 int spokes; /* how many spokes inside, if any */
57 int nubs; /* how many little nubbly bits, if any */
58 double spoke_thickness; /* spoke versus hole */
59 GLfloat wobble; /* factory defect! */
60 int motion_blur_p; /* whether it's spinning too fast to draw */
61 int polygons; /* how many polys in this gear */
63 double ratio; /* gearing ratio with previous gears */
64 double rpm; /* approximate revolutions per minute */
66 Bool base_p; /* whether this gear begins a new train */
67 int coax_p; /* whether this is one of a pair of bound gears.
68 1 for first, 2 for second. */
69 double coax_thickness; /* thickness of the other gear in the pair */
70 enum { SMALL, MEDIUM, LARGE } size; /* Controls complexity of mesh. */
79 GLXContext *glx_context;
80 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
81 GLfloat vp_width, vp_height;
82 GLfloat render_left, render_right; /* area in which gears are displayed */
83 GLfloat layout_left, layout_right; /* layout region, on the right side */
89 trackball_state *trackball;
91 unsigned long mouse_gear_id;
93 XFontStruct *xfont1, *xfont2, *xfont3;
94 GLuint font1_dlist, font2_dlist, font3_dlist;
98 GLfloat plane_displacement; /* distance between coaxial gears */
100 int debug_size_failures; /* for debugging messages */
101 int debug_position_failures;
102 unsigned long current_length; /* gear count in current train */
103 unsigned long current_blur_length; /* how long have we been blurring? */
105 } pinion_configuration;
108 static pinion_configuration *pps = NULL;
110 /* command line arguments */
111 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
113 static Bool verbose_p = False; /* print progress on stderr */
114 static Bool debug_p = False; /* render as flat schematic */
116 /* internal debugging variables */
117 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
118 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
119 static Bool wire_all_p = False; /* in wireframe, do not abbreviate */
122 static XrmOptionDescRec opts[] = {
123 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
124 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
125 { "-size", ".gearSize", XrmoptionSepArg, 0 },
126 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
127 { "-debug", ".debug", XrmoptionNoArg, "True" },
128 { "-verbose",".verbose", XrmoptionNoArg, "True" },
131 static argtype vars[] = {
132 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
133 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
134 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
135 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
136 {&debug_p, "debug", "Debug", "False", t_Bool},
137 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
140 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
147 load_fonts (ModeInfo *mi)
149 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
150 load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
151 load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
152 load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
157 static void rpm_string (double rpm, char *s);
160 new_label (ModeInfo *mi)
162 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
167 if (pp->mouse_gear_id)
168 for (i = 0; i < pp->ngears; i++)
169 if (pp->gears[i]->id == pp->mouse_gear_id)
179 sprintf (label, "%d teeth\n", (int) g->nteeth);
180 rpm_string (g->rpm, label + strlen(label));
182 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
184 (g->size == SMALL ? "small" : g->size == MEDIUM ? "medium"
186 g->tooth_h * MI_HEIGHT(mi));
189 glNewList (pp->title_list, GL_COMPILE);
194 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
195 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
196 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
197 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
199 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
201 glColor3f (0.8, 0.8, 0);
202 print_gl_string (mi->dpy, f, fl,
203 mi->xgwa.width, mi->xgwa.height,
204 10, mi->xgwa.height - 10,
215 /* Find the gear in the scene that is farthest to the right or left.
218 farthest_gear (ModeInfo *mi, Bool left_p)
220 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
223 double x = (left_p ? 999999 : -999999);
224 for (i = 0; i < pp->ngears; i++)
226 gear *g = pp->gears[i];
227 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
228 if (left_p ? (x > gx) : (x < gx))
238 /* Compute the revolutions per minute of a gear.
241 compute_rpm (ModeInfo *mi, gear *g)
243 double fps, rpf, rps;
244 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
246 if (fps > 150) fps = 150; /* let's be reasonable... */
247 if (fps < 10) fps = 10;
249 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
250 rps = rpf * fps; /* rotations per second */
254 /* Prints the RPM into a string, doing fancy float formatting.
257 rpm_string (double rpm, char *s)
261 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
262 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
263 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
264 else sprintf (buf, "%.16f",rpm);
267 while (buf[L-1] == '0') buf[--L] = 0;
268 if (buf[L-1] == '.') buf[--L] = 0;
275 /* Which of the gear's inside rings is the biggest?
278 biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
280 double r0 = (g->r - g->tooth_h/2);
281 double r1 = g->inner_r;
282 double r2 = g->inner_r2;
283 double r3 = g->inner_r3;
284 double w1 = (r1 ? r0 - r1 : r0);
285 double w2 = (r2 ? r1 - r2 : 0);
286 double w3 = (r3 ? r2 - r3 : 0);
287 double h1 = g->thickness;
288 double h2 = g->thickness2;
289 double h3 = g->thickness3;
291 if (g->spokes) w2 = 0;
293 if (w1 > w2 && w1 > w3)
295 if (posP) *posP = (r0+r1)/2;
296 if (sizeP) *sizeP = w1;
297 if (heightP) *heightP = h1;
300 else if (w2 > w1 && w2 > w3)
302 if (posP) *posP = (r1+r2)/2;
303 if (sizeP) *sizeP = w2;
304 if (heightP) *heightP = h2;
309 if (posP) *posP = (r2+r3)/2;
310 if (sizeP) *sizeP = w3;
311 if (heightP) *heightP = h3;
321 /* Create and return a new gear sized for placement next to or on top of
322 the given parent gear (if any.) Returns 0 if out of memory.
325 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
327 gear *g = (gear *) calloc (1, sizeof (*g));
329 static unsigned long id = 0; /* only used in debugging output */
332 if (coaxial_p && !parent) abort();
338 if (loop_count > 1000)
339 /* The only time we loop in here is when making a coaxial gear, and
340 trying to pick a radius that is either significantly larger or
341 smaller than its parent. That shouldn't be hard, so something
342 must be really wrong if we can't do that in only a few tries.
346 /* Pick the size of the teeth.
348 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
350 g->tooth_w = parent->tooth_w;
351 g->tooth_h = parent->tooth_h;
352 g->thickness = parent->thickness;
353 g->thickness2 = parent->thickness2;
354 g->thickness3 = parent->thickness3;
356 else /* gears that begin trains get any size they want */
358 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
359 g->tooth_w = 0.007 * scale;
360 g->tooth_h = 0.005 * scale;
361 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
362 g->thickness2 = g->thickness / 4;
363 g->thickness3 = g->thickness;
366 /* Pick the number of teeth, and thus, the radius.
372 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
374 if (g->nteeth < 7 && (random() % 5) != 0)
375 goto AGAIN; /* Let's make very small tooth-counts more rare */
377 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
378 g->r = c / (M_PI * 2); /* c = 2 pi r */
384 if (! coaxial_p) break; /* yes */
385 if (g->nteeth == parent->nteeth) continue; /* ugly */
386 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
387 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
392 g->color[0] = 0.5 + frand(0.5);
393 g->color[1] = 0.5 + frand(0.5);
394 g->color[2] = 0.5 + frand(0.5);
397 g->color2[0] = g->color[0] * 0.85;
398 g->color2[1] = g->color[1] * 0.85;
399 g->color2[2] = g->color[2] * 0.85;
400 g->color2[3] = g->color[3];
403 /* Decide on shape of gear interior:
404 - just a ring with teeth;
405 - that, plus a thinner in-set "plate" in the middle;
406 - that, plus a thin raised "lip" on the inner plate;
407 - or, a wide lip (really, a thicker third inner plate.)
409 if ((random() % 10) == 0)
411 /* inner_r can go all the way in; there's no inset disc. */
412 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
418 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
419 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
420 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
423 if (g->inner_r2 > (g->r * 0.2))
425 int nn = (random() % 10);
427 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
428 else if (nn <= 7 && g->inner_r2 >= 0.1)
429 g->inner_r3 = g->inner_r2 - 0.01;
433 /* Coaxial gears need to have the same innermost hole size (for the axle.)
434 Use whichever of the two is smaller. (Modifies parent.)
438 double hole1 = (g->inner_r3 ? g->inner_r3 :
439 g->inner_r2 ? g->inner_r2 :
441 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
442 parent->inner_r2 ? parent->inner_r2 :
444 double hole = (hole1 < hole2 ? hole1 : hole2);
445 if (hole <= 0) abort();
447 if (g->inner_r3) g->inner_r3 = hole;
448 else if (g->inner_r2) g->inner_r2 = hole;
449 else g->inner_r = hole;
451 if (parent->inner_r3) parent->inner_r3 = hole;
452 else if (parent->inner_r2) parent->inner_r2 = hole;
453 else parent->inner_r = hole;
456 /* If we have three discs, sometimes make the middle disc be spokes.
458 if (g->inner_r3 && ((random() % 5) == 0))
460 g->spokes = 2 + BELLRAND (5);
461 g->spoke_thickness = 1 + frand(7.0);
462 if (g->spokes == 2 && g->spoke_thickness < 2)
463 g->spoke_thickness += 1;
466 /* Sometimes add little nubbly bits, if there is room.
471 biggest_ring (g, 0, &size, 0);
472 if (size > g->r * 0.2 && (random() % 5) == 0)
474 g->nubs = 1 + (random() % 16);
475 if (g->nubs > 8) g->nubs = 1;
479 if (g->inner_r3 > g->inner_r2) abort();
480 if (g->inner_r2 > g->inner_r) abort();
481 if (g->inner_r > g->r) abort();
483 /* Decide how complex the polygon model should be.
486 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
487 if (pix <= 2.5) g->size = SMALL;
488 else if (pix <= 3.5) g->size = MEDIUM;
489 else g->size = LARGE;
498 /* Given a newly-created gear, place it next to its parent in the scene,
499 with its teeth meshed and the proper velocity. Returns False if it
500 didn't work. (Call this a bunch of times until either it works, or
501 you decide it's probably not going to.)
504 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
506 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
508 /* If this gear takes up more than 1/3rd of the screen, it's no good.
510 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
511 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
513 if (verbose_p && debug_placement_p && 0)
515 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
517 (g->r + g->tooth_h), gear_size,
518 pp->vp_width, pp->vp_height);
519 pp->debug_size_failures++;
523 /* Compute this gear's velocity.
527 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
528 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
532 g->ratio = parent->ratio; /* bound gears have the same ratio */
534 g->rpm = parent->rpm;
535 g->wobble = parent->wobble;
539 /* Gearing ratio is the ratio of the number of teeth to previous gear
540 (which is also the ratio of the circumferences.)
542 g->ratio = (double) parent->nteeth / (double) g->nteeth;
544 /* Set our initial rotation to match that of the previous gear,
545 multiplied by the gearing ratio. (This is finessed later,
546 once we know the exact position of the gear relative to its
549 g->th = -(parent->th * g->ratio);
551 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
553 double off = (180.0 / g->nteeth);
560 /* ratios are cumulative for all gears in the train. */
561 g->ratio *= parent->ratio;
565 /* Place the gear relative to the parent.
570 gear *rg = farthest_gear (mi, False);
571 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
572 if (right < pp->layout_left) /* place off screen */
573 right = pp->layout_left;
575 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
579 if (debug_one_gear_p)
584 double off = pp->plane_displacement;
588 g->z = parent->z + (g->r > parent->r /* small gear on top */
591 if (parent->r > g->r) /* mark which is top and which is bottom */
595 parent->wobble = 0; /* looks bad when axle moves */
604 g->coax_thickness = parent->thickness;
605 parent->coax_thickness = g->thickness;
607 /* Don't let the train get too close to or far from the screen.
608 If we're getting too close, give up on this gear.
609 (But getting very far away is fine.)
611 if (g->z >= off * 4 ||
614 if (verbose_p && debug_placement_p)
615 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
617 pp->debug_position_failures++;
621 else /* position it somewhere next to the parent. */
623 double r_off = parent->r + g->r;
626 if ((random() % 3) != 0)
627 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
629 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
631 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
632 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
635 /* If the angle we picked would have positioned this gear
636 more than halfway off screen, that's no good. */
637 if (g->y > pp->vp_top ||
638 g->y < pp->vp_bottom)
640 if (verbose_p && debug_placement_p)
641 fprintf (stderr, "%s: placement: out of bounds: %s\n",
642 progname, (g->y > pp->vp_top ? "top" : "bottom"));
643 pp->debug_position_failures++;
647 /* avoid accidentally changing sign of "th" in the math below. */
648 g->th += (g->th > 0 ? 360 : -360);
650 /* Adjust the rotation of the gear so that its teeth line up with its
651 parent, based on the position of the gear and the current rotation
655 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
656 double g_c = 2 * M_PI * g->r; /* circumference of g */
658 double p_t = p_c * (angle/360.0); /* distance travelled along
659 circumference of parent when
660 moving "angle" degrees along
662 double g_rat = p_t / g_c; /* if travelling that distance
663 along circumference of g,
664 ratio of g's circumference
666 double g_th = 360.0 * g_rat; /* that ratio in degrees */
668 g->th += angle + g_th;
672 if (debug_one_gear_p)
678 /* If the position we picked for this gear would cause it to already
679 be visible on the screen, give up. This can happen when the train
680 is growing backwards, and we don't want to see gears flash into
683 if (g->x - g->r - g->tooth_h < pp->render_right)
685 if (verbose_p && debug_placement_p)
686 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
687 pp->debug_position_failures++;
691 /* If the position we picked for this gear causes it to overlap
692 with any earlier gear in the train, give up.
697 for (i = pp->ngears-1; i >= 0; i--)
699 gear *og = pp->gears[i];
701 if (og == g) continue;
702 if (og == parent) continue;
703 if (g->z != og->z) continue; /* Ignore unless on same layer */
705 /* Collision detection without sqrt:
706 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
707 d < r1 + r2 d^2 < (r1 + r2)^2
709 if (((g->x - og->x) * (g->x - og->x) +
710 (g->y - og->y) * (g->y - og->y)) <
711 ((g->r + g->tooth_h + og->r + og->tooth_h) *
712 (g->r + g->tooth_h + og->r + og->tooth_h)))
714 if (verbose_p && debug_placement_p)
715 fprintf (stderr, "%s: placement: collision with %lu\n",
717 pp->debug_position_failures++;
726 /* Make deeper gears be darker.
729 double depth = g->z / pp->plane_displacement;
730 double brightness = 1 + (depth / 6);
732 if (brightness < limit) brightness = limit;
733 if (brightness > 1/limit) brightness = 1/limit;
734 g->color[0] *= brightness;
735 g->color[1] *= brightness;
736 g->color[2] *= brightness;
737 g->color2[0] *= brightness;
738 g->color2[1] *= brightness;
739 g->color2[2] *= brightness;
742 /* If a single frame of animation would cause the gear to rotate by
743 more than 1/2 the size of a single tooth, then it won't look right:
744 the gear will appear to be turning at some lower harmonic of its
748 double ratio = g->ratio * spin_speed;
749 double blur_limit = 180.0 / g->nteeth;
751 if (ratio > blur_limit)
752 g->motion_blur_p = 1;
756 /* ride until the wheels fall off... */
757 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
758 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
759 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
760 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
761 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
762 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
773 glDeleteLists (g->dlist, 1);
778 /* Make a new gear, place it next to its parent in the scene,
779 with its teeth meshed and the proper velocity. Returns the gear;
780 or 0 if it didn't work. (Call this a bunch of times until either
781 it works, or you decide it's probably not going to.)
784 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
786 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
793 if (loop_count >= 100)
801 g = new_gear (mi, parent, coaxial_p);
802 if (!g) return 0; /* out of memory? */
804 if (place_gear (mi, g, parent, coaxial_p))
810 /* We got a gear, and it is properly positioned.
811 Insert it in the scene.
813 if (pp->ngears + 2 >= pp->gears_size)
815 pp->gears_size += 100;
816 pp->gears = (gear **) realloc (pp->gears,
817 pp->gears_size * sizeof (*pp->gears));
820 fprintf (stderr, "%s: out of memory (%d gears)\n",
821 progname, pp->gears_size);
825 pp->gears[pp->ngears++] = g;
830 static void delete_gear (ModeInfo *mi, gear *g);
833 push_gear (ModeInfo *mi)
835 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
837 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
839 Bool tried_coaxial_p = False;
840 Bool coaxial_p = False;
841 Bool last_ditch_coax_p = False;
844 pp->debug_size_failures = 0;
845 pp->debug_position_failures = 0;
849 if (loop_count > 100) abort(); /* we're doomed! */
853 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
854 reset things to a sane velocity.
856 10,000 RPM at 30 FPS = 5.5 rotations per frame.
857 1,000 RPM at 30 FPS = 0.5 rotations per frame.
858 600 RPM at 30 FPS = 3 frames per rotation.
859 200 RPM at 30 FPS = 9 frames per rotation.
860 100 RPM at 30 FPS = 18 frames per rotation.
861 50 RPM at 30 FPS = 36 frames per rotation.
862 10 RPM at 30 FPS = 3 sec per rotation.
863 1 RPM at 30 FPS = 30 sec per rotation.
864 .5 RPM at 30 FPS = 1 min per rotation.
865 .1 RPM at 30 FPS = 5 min per rotation.
867 if (parent && parent->rpm > max_rpm)
872 rpm_string (parent->rpm, buf);
873 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
878 /* If the last N gears we've placed have all been motion-blurred, then
879 it's a safe guess that we've wandered off into the woods and aren't
880 coming back. Bail on this train.
882 if (pp->current_blur_length >= 10)
885 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
891 /* Sometimes, try to make a coaxial gear.
893 if (parent && !parent->coax_p && (random() % 40) == 0)
895 tried_coaxial_p = True;
897 g = place_new_gear (mi, parent, coaxial_p);
900 /* Try to make a regular gear.
905 g = place_new_gear (mi, parent, coaxial_p);
908 /* If we couldn't make a regular gear, then try to make a coxial gear
909 (unless we already tried that.)
911 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
913 tried_coaxial_p = True;
915 g = place_new_gear (mi, parent, coaxial_p);
917 last_ditch_coax_p = True;
920 /* If we couldn't do that either, then the train has hit a dead end:
927 fprintf (stderr, "%s: dead end!\n\n", progname);
929 g = place_new_gear (mi, parent, coaxial_p);
934 /* Unable to make/place any gears at all!
935 This can happen if we've backed ourself into a corner very near
936 the top-right or bottom-right corner of the growth zone.
937 It's time to add a gear, but there's no room to add one!
938 In that case, let's just wipe all the gears that are in the
939 growth zone and try again.
943 if (verbose_p && debug_placement_p)
945 "%s: placement: resetting growth zone! "
946 "failed: %d size, %d pos\n",
948 pp->debug_size_failures, pp->debug_position_failures);
949 for (i = pp->ngears-1; i >= 0; i--)
951 gear *g = pp->gears[i];
952 if (g->x - g->r - g->tooth_h < pp->render_left)
960 if (g->x != parent->x) abort();
961 if (g->y != parent->y) abort();
962 if (g->z == parent->z) abort();
963 if (g->r == parent->r) abort();
964 if (g->th != parent->th) abort();
965 if (g->ratio != parent->ratio) abort();
966 if (g->rpm != parent->rpm) abort();
971 fprintf (stderr, "%s: %5lu ", progname, g->id);
973 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
974 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
975 g->coax_p ? '1' : ' '),
978 fprintf (stderr, " %2d%%",
979 (int) (g->r * 2 * 100 / pp->vp_height));
980 fprintf (stderr, " %2d teeth", (int) g->nteeth);
981 fprintf (stderr, " %3.0f rpm;", g->rpm);
984 char buf1[50], buf2[50], buf3[100];
985 *buf1 = 0; *buf2 = 0; *buf3 = 0;
986 if (pp->debug_size_failures)
987 sprintf (buf1, "%3d sz", pp->debug_size_failures);
988 if (pp->debug_position_failures)
989 sprintf (buf2, "%2d pos", pp->debug_position_failures);
991 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
992 fprintf (stderr, "%-21s", buf3);
995 if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
996 fprintf (stderr, "\n");
1000 pp->current_length = 1;
1002 pp->current_length++;
1004 if (g->motion_blur_p)
1005 pp->current_blur_length++;
1007 pp->current_blur_length = 0;
1012 /* Remove the given gear from the scene and free it.
1015 delete_gear (ModeInfo *mi, gear *g)
1017 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1020 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
1021 if (pp->gears[i] == g) break;
1022 if (pp->gears[i] != g) abort();
1024 for (; i < pp->ngears-1; i++) /* pull later entries forward */
1025 pp->gears[i] = pp->gears[i+1];
1032 /* Update the position of each gear in the scene.
1035 scroll_gears (ModeInfo *mi)
1037 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1040 for (i = 0; i < pp->ngears; i++)
1041 pp->gears[i]->x -= (scroll_speed * 0.002);
1043 /* if the right edge of any gear is off screen to the left, delete it.
1045 for (i = pp->ngears-1; i >= 0; i--)
1047 gear *g = pp->gears[i];
1048 if (g->x + g->r + g->tooth_h < pp->render_left)
1049 delete_gear (mi, g);
1052 /* if the right edge of the last-added gear is left of the right edge
1053 of the layout area, add another gear.
1058 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1059 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1064 if (debug_one_gear_p) break;
1068 if (i > 1 && verbose_p)
1069 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1074 /* Update the rotation of each gear in the scene.
1077 spin_gears (ModeInfo *mi)
1079 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1082 for (i = 0; i < pp->ngears; i++)
1084 gear *g = pp->gears[i];
1085 double off = (g->ratio * spin_speed);
1095 /* Run the animation fast (without displaying anything) until the first
1096 gear is just about to come on screen. This is to avoid a big delay
1097 with a blank screen when -scroll is low.
1102 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1103 if (debug_one_gear_p) return;
1106 gear *g = farthest_gear (mi, True);
1107 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1115 /* Rendering the 3D objects into the scene.
1119 /* Draws an uncapped tube of the given radius extending from top to bottom,
1120 with faces pointing either in or out.
1123 draw_ring (ModeInfo *mi, int segments,
1124 GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1128 Bool wire_p = MI_IS_WIREFRAME(mi);
1129 GLfloat width = M_PI * 2 / segments;
1133 glFrontFace (in_p ? GL_CCW : GL_CW);
1134 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1135 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1137 GLfloat th = i * width;
1138 GLfloat cth = cos(th);
1139 GLfloat sth = sin(th);
1141 glNormal3f (-cth, -sth, 0);
1143 glNormal3f (cth, sth, 0);
1144 glVertex3f (cth * r, sth * r, top);
1145 glVertex3f (cth * r, sth * r, bottom);
1153 glBegin (GL_LINE_LOOP);
1154 for (i = 0; i < segments; i++)
1156 GLfloat th = i * width;
1157 glVertex3f (cos(th) * r, sin(th) * r, top);
1160 glBegin (GL_LINE_LOOP);
1161 for (i = 0; i < segments; i++)
1163 GLfloat th = i * width;
1164 glVertex3f (cos(th) * r, sin(th) * r, bottom);
1173 /* Draws a donut-shaped disc between the given radii,
1174 with faces pointing either up or down.
1175 The first radius may be 0, in which case, a filled disc is drawn.
1178 draw_disc (ModeInfo *mi, int segments,
1179 GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1183 Bool wire_p = MI_IS_WIREFRAME(mi);
1184 GLfloat width = M_PI * 2 / segments;
1186 if (ra < 0) abort();
1187 if (rb <= 0) abort();
1190 glFrontFace (up_p ? GL_CW : GL_CCW);
1192 glFrontFace (up_p ? GL_CCW : GL_CW);
1195 glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1197 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1199 glNormal3f (0, 0, (up_p ? -1 : 1));
1201 if (ra == 0 && !wire_p)
1202 glVertex3f (0, 0, z);
1204 for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1206 GLfloat th = i * width;
1207 GLfloat cth = cos(th);
1208 GLfloat sth = sin(th);
1209 if (wire_p || ra != 0)
1210 glVertex3f (cth * ra, sth * ra, z);
1211 glVertex3f (cth * rb, sth * rb, z);
1219 /* Draws N thick radial lines between the given radii,
1220 with faces pointing either up or down.
1223 draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1224 GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1228 Bool wire_p = MI_IS_WIREFRAME(mi);
1231 int insegs, outsegs;
1235 if (ra <= 0 || rb <= 0) abort();
1239 while (segments2 < segments) /* need a multiple of N >= segments */
1240 segments2 += n; /* (yes, this is a moronic way to find that) */
1242 insegs = ((float) (segments2 / n) + 0.5) / thickness;
1243 outsegs = (segments2 / n) - insegs;
1244 if (insegs <= 0) insegs = 1;
1245 if (outsegs <= 0) outsegs = 1;
1247 segments2 = (insegs + outsegs) * n;
1248 width = M_PI * 2 / segments2;
1252 for (i = 0; i < segments2; i++, tick++)
1254 GLfloat th1 = i * width;
1255 GLfloat th2 = th1 + width;
1256 GLfloat cth1 = cos(th1);
1257 GLfloat sth1 = sin(th1);
1258 GLfloat cth2 = cos(th2);
1259 GLfloat sth2 = sin(th2);
1262 int changed = (i == 0);
1264 if (state == 0 && tick == insegs)
1265 tick = 0, state = 1, changed = 1;
1266 else if (state == 1 && tick == outsegs)
1267 tick = 0, state = 0, changed = 1;
1269 if ((state == 1 || /* in */
1270 (state == 0 && changed)) &&
1271 (!wire_p || wire_all_p))
1274 glFrontFace (GL_CCW);
1275 glBegin (wire_p ? GL_LINES : GL_QUADS);
1276 glNormal3f (0, 0, -1);
1277 glVertex3f (cth1 * ra, sth1 * ra, z1);
1278 glVertex3f (cth1 * rb, sth1 * rb, z1);
1279 glVertex3f (cth2 * rb, sth2 * rb, z1);
1280 glVertex3f (cth2 * ra, sth2 * ra, z1);
1285 glFrontFace (GL_CW);
1286 glBegin (wire_p ? GL_LINES : GL_QUADS);
1287 glNormal3f (0, 0, 1);
1288 glVertex3f (cth1 * ra, sth1 * ra, z2);
1289 glVertex3f (cth1 * rb, sth1 * rb, z2);
1290 glVertex3f (cth2 * rb, sth2 * rb, z2);
1291 glVertex3f (cth2 * ra, sth2 * ra, z2);
1296 if (state == 1 && changed) /* entering "in" state */
1299 glFrontFace (GL_CW);
1300 glBegin (wire_p ? GL_LINES : GL_QUADS);
1301 do_normal (cth1 * rb, sth1 * rb, z1,
1302 cth1 * ra, sth1 * ra, z1,
1303 cth1 * rb, sth1 * rb, z2);
1304 glVertex3f (cth1 * ra, sth1 * ra, z1);
1305 glVertex3f (cth1 * rb, sth1 * rb, z1);
1306 glVertex3f (cth1 * rb, sth1 * rb, z2);
1307 glVertex3f (cth1 * ra, sth1 * ra, z2);
1312 if (state == 0 && changed) /* entering "out" state */
1315 glFrontFace (GL_CCW);
1316 glBegin (wire_p ? GL_LINES : GL_QUADS);
1317 do_normal (cth2 * ra, sth2 * ra, z1,
1318 cth2 * rb, sth2 * rb, z1,
1319 cth2 * rb, sth2 * rb, z2);
1320 glVertex3f (cth2 * ra, sth2 * ra, z1);
1321 glVertex3f (cth2 * rb, sth2 * rb, z1);
1322 glVertex3f (cth2 * rb, sth2 * rb, z2);
1323 glVertex3f (cth2 * ra, sth2 * ra, z2);
1335 /* Draws some bumps (embedded cylinders) on the gear.
1338 draw_gear_nubs (ModeInfo *mi, gear *g)
1340 Bool wire_p = MI_IS_WIREFRAME(mi);
1343 int steps = (g->size != LARGE ? 5 : 20);
1344 double r, size, height;
1349 if (! g->nubs) return 0;
1351 which = biggest_ring (g, &r, &size, &height);
1355 cc = (which == 1 ? g->color : g->color2);
1356 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1358 width = M_PI * 2 / g->nubs;
1359 off = M_PI / (g->nteeth * 2); /* align first nub with a tooth */
1361 for (i = 0; i < g->nubs; i++)
1363 GLfloat th = (i * width) + off;
1365 glTranslatef (cos(th) * r, sin(th) * r, 0);
1367 if (wire_p && !wire_all_p)
1368 polys += draw_ring (mi, (g->size == LARGE ? steps/2 : steps),
1372 polys += draw_disc (mi, steps, 0, size, -height, True);
1373 polys += draw_disc (mi, steps, 0, size, height, False);
1374 polys += draw_ring (mi, steps, size, -height, height, False);
1383 /* Draws a much simpler representation of a gear.
1386 draw_gear_schematic (ModeInfo *mi, gear *g)
1388 Bool wire_p = MI_IS_WIREFRAME(mi);
1391 GLfloat width = M_PI * 2 / g->nteeth;
1393 if (!wire_p) glDisable(GL_LIGHTING);
1394 glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1397 for (i = 0; i < g->nteeth; i++)
1399 GLfloat th = (i * width) + (width/4);
1400 glVertex3f (0, 0, -g->thickness/2);
1401 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1406 glBegin (GL_LINE_LOOP);
1407 for (i = 0; i < g->nteeth; i++)
1409 GLfloat th = (i * width) + (width/4);
1410 glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1415 if (!wire_p) glEnable(GL_LIGHTING);
1420 /* Renders all the interior (non-toothy) parts of a gear:
1421 the discs, axles, etc.
1424 draw_gear_interior (ModeInfo *mi, gear *g)
1426 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1427 Bool wire_p = MI_IS_WIREFRAME(mi);
1430 int steps = g->nteeth * 2;
1431 if (steps < 10) steps = 10;
1432 if ((wire_p && !wire_all_p) || g->size != LARGE) steps /= 2;
1433 if (g->size != LARGE && steps > 16) steps = 16;
1435 /* ring 1 (facing in) is done in draw_gear_teeth */
1437 /* ring 2 (facing in) and disc 2
1441 GLfloat ra = g->inner_r * 1.04; /* slightly larger than inner_r */
1442 GLfloat rb = g->inner_r2; /* since the points don't line up */
1443 GLfloat za = -g->thickness2/2;
1444 GLfloat zb = g->thickness2/2;
1446 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1448 if ((g->coax_p != 1 && !g->inner_r3) ||
1449 (wire_p && wire_all_p))
1450 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1452 if (wire_p && wire_all_p)
1453 polys += draw_ring (mi, steps, ra, za, zb, True); /* ring facing in */
1456 polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1457 steps, ra, rb, za, zb);
1458 else if (!wire_p || wire_all_p)
1460 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1461 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1465 /* ring 3 (facing in and out) and disc 3
1469 GLfloat ra = g->inner_r2;
1470 GLfloat rb = g->inner_r3;
1471 GLfloat za = -g->thickness3/2;
1472 GLfloat zb = g->thickness3/2;
1474 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1476 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1478 if (g->coax_p != 1 || (wire_p && wire_all_p))
1479 polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1481 if (!wire_p || wire_all_p)
1483 polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1484 polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1492 GLfloat cap_height = g->coax_thickness/3;
1494 GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1495 g->inner_r2 ? g->inner_r2 :
1497 GLfloat za = -(g->thickness/2 + cap_height);
1498 GLfloat zb = g->coax_thickness/2 + pp->plane_displacement + cap_height;
1500 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1502 if (wire_p && !wire_all_p) steps /= 2;
1504 polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1506 if (!wire_p || wire_all_p)
1508 polys += draw_disc (mi, steps, 0, ra, za, True); /* top plate */
1509 polys += draw_disc (mi, steps, 0, ra, zb, False); /* bottom plate */
1516 /* gear_teeth_geometry computes the vertices and normals of the teeth
1517 of a gear. This is the heavy lifting: there are a ton of polygons
1518 around the perimiter of a gear, and the normals are difficult (not
1519 radial or right angles.)
1521 It would be nice if we could cache this, but the numbers are
1522 different for essentially every gear:
1524 - Every gear has a different inner_r, so the vertices of the
1525 inner ring (and thus, the triangle fans on the top and bottom
1526 faces) are different in a non-scalable way.
1528 - If the ratio between tooth_w and tooth_h changes, the normals
1529 on the outside edges of the teeth are invalid (this can happen
1530 every time we start a new train.)
1532 So instead, we rely on OpenGL display lists to do the cacheing for
1533 us -- we only compute all these normals once per gear, instead of
1534 once per gear per frame.
1540 XYZ *fnormals; /* face normals */
1541 XYZ *pnormals; /* point normals */
1546 tooth_normals (tooth_face *f)
1550 /* Compute the face normals for each facet. */
1551 for (i = 0; i < f->npoints; i++)
1555 int b = (i == f->npoints-1 ? 0 : i+1);
1560 f->fnormals[i] = calc_normal (p1, p2, p3);
1563 /* From the face normals, compute the vertex normals
1564 (by averaging the normals of adjascent faces.)
1566 for (i = 0; i < f->npoints; i++)
1568 int a = (i == 0 ? f->npoints-1 : i-1);
1570 XYZ n1 = f->fnormals[a]; /* normal of [i-1 - i] face */
1571 XYZ n2 = f->fnormals[b]; /* normal of [i - i+1] face */
1572 f->pnormals[i].x = (n1.x + n2.x) / 2;
1573 f->pnormals[i].y = (n1.y + n2.y) / 2;
1574 f->pnormals[i].z = (n1.z + n2.z) / 2;
1580 gear_teeth_geometry (ModeInfo *mi, gear *g,
1581 tooth_face *orim, /* outer rim (the teeth) */
1582 tooth_face *irim) /* inner rim (the hole) */
1585 int ppt = 9; /* max points per tooth */
1586 GLfloat width = M_PI * 2 / g->nteeth;
1587 GLfloat rh = g->tooth_h;
1590 /* Approximate shape of an "involute" gear tooth.
1593 th0 th1 th2 th3 th4 th5 th6 th7 th8 th9 th10
1594 : : : : : : : : : : :
1595 : : : : : : : : : : :
1596 r0 ........:..:..:...___________...:..:..:......:......:..
1597 : : : /: : :\ : : : : :
1598 : : : / : : : \ : : : : :
1599 : : :/ : : : \: : : : :
1600 r1 ........:.....@...:....:....:...@..:..:......:......:..
1601 : : @: : : : :@ : : : :
1602 (R) ...........:...@.:...:....:....:...:.@..........:......:......
1603 : :@ : : : : : @: : : :
1604 r2 ........:..@..:...:....:....:...:..@:........:......:..
1605 : /: : : : : : :\ : : :
1606 :/ : : : : : : : \: : : /
1607 r3 ......__/..:..:...:....:....:...:..:..\______________/
1608 : : : : : : : : : : :
1609 | : : : : : : : | : :
1610 : : : : : : : : : : :
1611 | : : : : : : : | : :
1612 r4 ......__:_____________________________:________________
1619 r[0] = R + (rh * 0.5);
1620 r[1] = R + (rh * 0.25);
1621 r[2] = R - (r[1]-R);
1622 r[3] = R - (r[0]-R);
1625 th[0] = -tw * (g->size == SMALL ? 0.5 : g->size == MEDIUM ? 0.41 : 0.45);
1627 th[2] = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
1628 th[3] = -tw * (g->size == MEDIUM ? 0.1 : 0.04);
1635 th[10]= th[0] + width;
1638 orim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
1639 orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
1640 orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
1643 irim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
1644 irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
1645 irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
1647 if (!orim->points || !orim->pnormals || !orim->fnormals ||
1648 !irim->points || !irim->pnormals || !irim->fnormals)
1650 fprintf (stderr, "%s: out of memory\n", progname);
1654 /* First, compute the coordinates of every point used for every tooth.
1656 for (i = 0; i < g->nteeth; i++)
1658 GLfloat TH = (i * width) + (width/4);
1661 # define PUSH(OPR,IPR,PTH) \
1662 orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
1663 orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
1665 irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
1666 irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
1669 if (g->size == SMALL)
1671 PUSH(3, 4, 0); /* tooth left 1 */
1672 PUSH(0, 4, 4); /* tooth top middle */
1674 else if (g->size == MEDIUM)
1676 PUSH(3, 4, 0); /* tooth left 1 */
1677 PUSH(0, 4, 3); /* tooth top left */
1678 PUSH(0, 4, 5); /* tooth top right */
1679 PUSH(3, 4, 8); /* tooth right 3 */
1681 else if (g->size == LARGE)
1683 PUSH(3, 4, 0); /* tooth left 1 */
1684 PUSH(2, 4, 1); /* tooth left 2 */
1685 PUSH(1, 4, 2); /* tooth left 3 */
1686 PUSH(0, 4, 3); /* tooth top left */
1687 PUSH(0, 4, 5); /* tooth top right */
1688 PUSH(1, 4, 6); /* tooth right 1 */
1689 PUSH(2, 4, 7); /* tooth right 2 */
1690 PUSH(3, 4, 8); /* tooth right 3 */
1691 PUSH(3, 4, 9); /* gap top */
1697 if (i == 0 && orim->npoints > ppt) abort(); /* go update "ppt"! */
1700 tooth_normals (orim);
1701 tooth_normals (irim);
1705 /* Renders all teeth of a gear.
1708 draw_gear_teeth (ModeInfo *mi, gear *g)
1710 Bool wire_p = MI_IS_WIREFRAME(mi);
1711 Bool show_normals_p = False;
1715 GLfloat z1 = -g->thickness/2;
1716 GLfloat z2 = g->thickness/2;
1718 tooth_face orim, irim;
1719 gear_teeth_geometry (mi, g, &orim, &irim);
1721 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1723 /* Draw the outer rim (the teeth)
1724 (In wire mode, this draws just the upright lines.)
1726 glFrontFace (GL_CW);
1727 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1728 for (i = 0; i < orim.npoints; i++)
1730 glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
1731 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1732 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1734 /* Show the face normal vectors */
1735 if (wire_p && show_normals_p)
1737 XYZ n = orim.fnormals[i];
1739 int b = (i == orim.npoints-1 ? 0 : i+1);
1740 GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
1741 GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
1742 GLfloat z = (z1 + z2) / 2;
1743 glVertex3f (x, y, z);
1744 glVertex3f (x + n.x, y + n.y, z);
1747 /* Show the vertex normal vectors */
1748 if (wire_p && show_normals_p)
1750 XYZ n = orim.pnormals[i];
1751 GLfloat x = orim.points[i].x;
1752 GLfloat y = orim.points[i].y;
1753 GLfloat z = (z1 + z2) / 2;
1754 glVertex3f (x, y, z);
1755 glVertex3f (x + n.x, y + n.y, z);
1759 if (!wire_p) /* close the quad loop */
1761 glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
1762 glVertex3f (orim.points[0].x, orim.points[0].y, z1);
1763 glVertex3f (orim.points[0].x, orim.points[0].y, z2);
1765 polys += orim.npoints;
1768 /* Draw the outer rim circles, in wire mode */
1771 glBegin (GL_LINE_LOOP);
1772 for (i = 0; i < orim.npoints; i++)
1773 glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1775 glBegin (GL_LINE_LOOP);
1776 for (i = 0; i < orim.npoints; i++)
1777 glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1782 /* Draw the inner rim (the hole)
1783 (In wire mode, this draws just the upright lines.)
1785 glFrontFace (GL_CCW);
1786 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1787 for (i = 0; i < irim.npoints; i++)
1789 glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
1790 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1791 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1793 /* Show the face normal vectors */
1794 if (wire_p && show_normals_p)
1796 XYZ n = irim.fnormals[i];
1798 int b = (i == irim.npoints-1 ? 0 : i+1);
1799 GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
1800 GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
1801 GLfloat z = (z1 + z2) / 2;
1802 glVertex3f (x, y, z);
1803 glVertex3f (x - n.x, y - n.y, z);
1806 /* Show the vertex normal vectors */
1807 if (wire_p && show_normals_p)
1809 XYZ n = irim.pnormals[i];
1810 GLfloat x = irim.points[i].x;
1811 GLfloat y = irim.points[i].y;
1812 GLfloat z = (z1 + z2) / 2;
1813 glVertex3f (x, y, z);
1814 glVertex3f (x - n.x, y - n.y, z);
1818 if (!wire_p) /* close the quad loop */
1820 glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
1821 glVertex3f (irim.points[0].x, irim.points[0].y, z1);
1822 glVertex3f (irim.points[0].x, irim.points[0].y, z2);
1824 polys += irim.npoints;
1827 /* Draw the inner rim circles, in wire mode
1831 glBegin (GL_LINE_LOOP);
1832 for (i = 0; i < irim.npoints; i++)
1833 glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1835 glBegin (GL_LINE_LOOP);
1836 for (i = 0; i < irim.npoints; i++)
1837 glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1842 /* Draw the side (the flat bit)
1844 if (!wire_p || wire_all_p)
1847 if (irim.npoints != orim.npoints) abort();
1848 for (z = z1; z <= z2; z += z2-z1)
1850 glFrontFace (z == z1 ? GL_CCW : GL_CW);
1851 glNormal3f (0, 0, z);
1852 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1853 for (i = 0; i < orim.npoints; i++)
1855 glVertex3f (orim.points[i].x, orim.points[i].y, z);
1856 glVertex3f (irim.points[i].x, irim.points[i].y, z);
1858 if (!wire_p) /* close the quad loop */
1860 glVertex3f (orim.points[0].x, orim.points[0].y, z);
1861 glVertex3f (irim.points[0].x, irim.points[0].y, z);
1863 polys += orim.npoints;
1869 free (irim.fnormals);
1870 free (irim.pnormals);
1873 free (orim.fnormals);
1874 free (orim.pnormals);
1880 /* Render one gear, unrotated at 0,0.
1883 draw_gear_1 (ModeInfo *mi, gear *g)
1885 Bool wire_p = MI_IS_WIREFRAME(mi);
1888 static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
1889 GLfloat shiny = 128.0;
1891 glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
1892 glMateriali (GL_FRONT, GL_SHININESS, shiny);
1893 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1894 glColor3f (g->color[0], g->color[1], g->color[2]);
1896 if (debug_p && wire_p)
1897 polys += draw_gear_schematic (mi, g);
1901 glRotatef (g->wobble, 1, 0, 0);
1902 polys += draw_gear_teeth (mi, g);
1903 polys += draw_gear_interior (mi, g);
1904 polys += draw_gear_nubs (mi, g);
1911 /* Render one gear in the proper position, creating the gear's
1912 display list first if necessary.
1915 draw_gear (ModeInfo *mi, int which)
1917 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1918 gear *g = pp->gears[which];
1921 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1922 g->x - g->r - g->tooth_h <= pp->render_right);
1924 if (!visible_p && !debug_p)
1929 g->dlist = glGenLists (1);
1932 /* I don't know how many display lists a GL implementation
1933 is supposed to provide, but hopefully it's more than
1934 "a few hundred", or we'll be in trouble...
1936 check_gl_error ("glGenLists");
1940 glNewList (g->dlist, GL_COMPILE);
1941 g->polygons = draw_gear_1 (mi, g);
1947 glTranslatef (g->x, g->y, g->z);
1949 if (g->motion_blur_p && !pp->button_down_p)
1951 /* If we're in motion-blur mode, then draw the gear so that each
1952 frame rotates it by exactly one half tooth-width, so that it
1953 looks flickery around the edges. But, revert to the normal
1954 way when the mouse button is down lest the user see overlapping
1957 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1963 glRotatef (th, 0, 0, 1);
1968 mi->polygon_count += draw_gear_schematic (mi, g);
1971 glCallList (g->dlist);
1972 mi->polygon_count += g->polygons;
1980 /* Render all gears.
1983 draw_gears (ModeInfo *mi)
1985 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1986 Bool wire_p = MI_IS_WIREFRAME(mi);
1989 glColor4f (1, 1, 0.8, 1);
1993 for (i = 0; i < pp->ngears; i++)
1996 /* draw a line connecting gears that are, uh, geared. */
1999 static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2001 GLfloat ox=0, oy=0, oz=0;
2003 if (!wire_p) glDisable(GL_LIGHTING);
2004 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2005 glColor3f (color[0], color[1], color[2]);
2007 for (i = 0; i < pp->ngears; i++)
2009 gear *g = pp->gears[i];
2010 glBegin(GL_LINE_STRIP);
2011 glVertex3f (g->x, g->y, g->z - off);
2012 glVertex3f (g->x, g->y, g->z + off);
2013 if (i > 0 && !g->base_p)
2014 glVertex3f (ox, oy, oz + off);
2020 if (!wire_p) glEnable(GL_LIGHTING);
2025 /* Mouse hit detection
2028 find_mouse_gear (ModeInfo *mi)
2030 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2031 int screen_width = MI_WIDTH (mi);
2032 int screen_height = MI_HEIGHT (mi);
2033 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2037 pp->mouse_gear_id = 0;
2039 /* Poll mouse position */
2044 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2045 &r, &c, &rx, &ry, &x, &y, &m);
2048 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2049 return; /* out of window */
2051 /* Run OpenGL hit detector */
2056 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
2057 glRenderMode (GL_SELECT);
2058 glMatrixMode (GL_PROJECTION);
2061 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
2062 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2063 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
2064 glMatrixMode (GL_MODELVIEW);
2066 draw_gears (mi); /* render into "select" buffer */
2068 glMatrixMode (GL_PROJECTION); /* restore old vp */
2070 glMatrixMode (GL_MODELVIEW);
2072 hits = glRenderMode (GL_RENDER); /* done selecting */
2077 GLuint name_count = 0;
2078 GLuint *p = (GLuint *) selbuf;
2082 for (i = 0; i < hits; i++)
2085 if (*p < min_z) /* find match closest to screen */
2094 if (name_count > 0) /* take first hit */
2095 pp->mouse_gear_id = pnames[0];
2101 /* Window management, etc
2104 reshape_pinion (ModeInfo *mi, int width, int height)
2106 GLfloat h = (GLfloat) height / (GLfloat) width;
2107 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2109 glViewport (0, 0, (GLint) width, (GLint) height);
2111 glMatrixMode(GL_PROJECTION);
2113 gluPerspective (30.0, 1/h, 1.0, 100.0);
2115 glMatrixMode(GL_MODELVIEW);
2117 gluLookAt( 0.0, 0.0, 30.0,
2121 glClear(GL_COLOR_BUFFER_BIT);
2124 GLfloat render_width, layout_width;
2125 pp->vp_height = 1.0;
2128 pp->vp_left = -pp->vp_width/2;
2129 pp->vp_right = pp->vp_width/2;
2130 pp->vp_top = pp->vp_height/2;
2131 pp->vp_bottom = -pp->vp_height/2;
2133 render_width = pp->vp_width * 2;
2134 layout_width = pp->vp_width * 0.8 * gear_size;
2136 pp->render_left = -render_width/2;
2137 pp->render_right = render_width/2;
2139 pp->layout_left = pp->render_right;
2140 pp->layout_right = pp->layout_left + layout_width;
2146 pinion_handle_event (ModeInfo *mi, XEvent *event)
2148 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2150 if (event->xany.type == ButtonPress &&
2151 event->xbutton.button == Button1)
2153 pp->button_down_p = True;
2154 gltrackball_start (pp->trackball,
2155 event->xbutton.x, event->xbutton.y,
2156 MI_WIDTH (mi), MI_HEIGHT (mi));
2159 else if (event->xany.type == ButtonRelease &&
2160 event->xbutton.button == Button1)
2162 pp->button_down_p = False;
2165 else if (event->xany.type == ButtonPress &&
2166 (event->xbutton.button == Button4 ||
2167 event->xbutton.button == Button5))
2169 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
2170 !!event->xbutton.state);
2173 else if (event->xany.type == MotionNotify &&
2176 gltrackball_track (pp->trackball,
2177 event->xmotion.x, event->xmotion.y,
2178 MI_WIDTH (mi), MI_HEIGHT (mi));
2181 else if (event->xany.type == KeyPress)
2185 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2186 if (c == ' ' && debug_one_gear_p && pp->ngears)
2188 delete_gear (mi, pp->gears[0]);
2198 init_pinion (ModeInfo *mi)
2200 pinion_configuration *pp;
2201 int wire = MI_IS_WIREFRAME(mi);
2204 pps = (pinion_configuration *)
2205 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2207 fprintf(stderr, "%s: out of memory\n", progname);
2211 pp = &pps[MI_SCREEN(mi)];
2214 pp = &pps[MI_SCREEN(mi)];
2216 pp->glx_context = init_GL(mi);
2219 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2221 pp->title_list = glGenLists (1);
2227 pp->plane_displacement = gear_size * 0.1;
2231 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2232 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2233 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2234 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2236 glEnable(GL_LIGHTING);
2237 glEnable(GL_LIGHT0);
2238 glEnable(GL_DEPTH_TEST);
2239 glEnable(GL_CULL_FACE);
2241 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2242 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2243 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2244 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2247 pp->trackball = gltrackball_init ();
2254 draw_pinion (ModeInfo *mi)
2256 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2257 Display *dpy = MI_DISPLAY(mi);
2258 Window window = MI_WINDOW(mi);
2259 Bool wire_p = MI_IS_WIREFRAME(mi);
2261 if (!pp->glx_context)
2264 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
2266 if (!pp->button_down_p)
2268 if (!debug_one_gear_p || pp->ngears == 0)
2273 glShadeModel(GL_SMOOTH);
2275 glEnable(GL_DEPTH_TEST);
2276 glEnable(GL_NORMALIZE);
2277 glEnable(GL_CULL_FACE);
2279 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2283 gltrackball_rotate (pp->trackball);
2284 mi->polygon_count = 0;
2286 glScalef (16, 16, 16); /* map vp_width/height to the screen */
2288 if (debug_one_gear_p) /* zoom in */
2290 else if (debug_p) /* show the "visible" and "layout" areas */
2292 GLfloat ow = pp->layout_right - pp->render_left;
2293 GLfloat rw = pp->render_right - pp->render_left;
2294 GLfloat s = (pp->vp_width / ow) * 0.85;
2296 glTranslatef (-(ow - rw) / 2, 0, 0);
2301 glScalef (s, s, s); /* zoom in a little more */
2302 glRotatef (-35, 1, 0, 0); /* tilt back */
2303 glRotatef ( 8, 0, 1, 0); /* tilt left */
2304 glTranslatef (0.02, 0.1, 0); /* pan up */
2311 if (!wire_p) glDisable(GL_LIGHTING);
2312 glColor3f (0.6, 0, 0);
2313 glBegin(GL_LINE_LOOP);
2314 glVertex3f (pp->render_left, pp->vp_top, 0);
2315 glVertex3f (pp->render_right, pp->vp_top, 0);
2316 glVertex3f (pp->render_right, pp->vp_bottom, 0);
2317 glVertex3f (pp->render_left, pp->vp_bottom, 0);
2319 glColor3f (0.4, 0, 0);
2321 glVertex3f (pp->vp_left, pp->vp_top, 0);
2322 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
2323 glVertex3f (pp->vp_right, pp->vp_top, 0);
2324 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
2326 glColor3f (0, 0.4, 0);
2327 glBegin(GL_LINE_LOOP);
2328 glVertex3f (pp->layout_left, pp->vp_top, 0);
2329 glVertex3f (pp->layout_right, pp->vp_top, 0);
2330 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2331 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
2333 if (!wire_p) glEnable(GL_LIGHTING);
2336 if (pp->draw_tick++ > 10) /* only do this every N frames */
2339 find_mouse_gear (mi);
2345 glCallList (pp->title_list);
2347 if (mi->fps_p) do_fps (mi);
2350 glXSwapBuffers(dpy, window);
2353 XSCREENSAVER_MODULE ("Pinion", pinion)