1 /* pinion, Copyright (c) 2004-2012 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 #define DEFAULTS "*delay: 15000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
15 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
16 "*titleFont2: -*-helvetica-medium-r-normal-*-120-*\n" \
17 "*titleFont3: -*-helvetica-medium-r-normal-*-80-*\n" \
19 # define refresh_pinion 0
20 # define release_pinion 0
22 #define countof(x) (sizeof((x))/sizeof((*x)))
25 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
27 #include "xlockmore.h"
29 #include "gltrackball.h"
34 #ifdef USE_GL /* whole file */
36 #define DEF_SPIN_SPEED "1.0"
37 #define DEF_SCROLL_SPEED "1.0"
38 #define DEF_GEAR_SIZE "1.0"
39 #define DEF_MAX_RPM "900"
42 GLXContext *glx_context;
43 GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
44 GLfloat vp_width, vp_height;
45 GLfloat render_left, render_right; /* area in which gears are displayed */
46 GLfloat layout_left, layout_right; /* layout region, on the right side */
52 trackball_state *trackball;
54 unsigned long mouse_gear_id;
57 XFontStruct *xfont1, *xfont2, *xfont3;
58 GLuint font1_dlist, font2_dlist, font3_dlist;
60 texture_font_data *font1, *font2, *font3;
66 GLfloat plane_displacement; /* distance between coaxial gears */
68 int debug_size_failures; /* for debugging messages */
69 int debug_position_failures;
70 unsigned long current_length; /* gear count in current train */
71 unsigned long current_blur_length; /* how long have we been blurring? */
73 } pinion_configuration;
76 static pinion_configuration *pps = NULL;
78 /* command line arguments */
79 static GLfloat spin_speed, scroll_speed, gear_size, max_rpm;
81 static Bool verbose_p = False; /* print progress on stderr */
82 static Bool debug_p = False; /* render as flat schematic */
84 /* internal debugging variables */
85 static Bool debug_placement_p = False; /* extreme verbosity on stderr */
86 static Bool debug_one_gear_p = False; /* draw one big stationary gear */
89 static XrmOptionDescRec opts[] = {
90 { "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
91 { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
92 { "-size", ".gearSize", XrmoptionSepArg, 0 },
93 { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
94 { "-debug", ".debug", XrmoptionNoArg, "True" },
95 { "-verbose",".verbose", XrmoptionNoArg, "True" },
98 static argtype vars[] = {
99 {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
100 {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
101 {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
102 {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
103 {&debug_p, "debug", "Debug", "False", t_Bool},
104 {&verbose_p, "verbose", "Verbose", "False", t_Bool},
107 ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL};
114 load_fonts (ModeInfo *mi)
116 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
117 # ifdef HAVE_GLBITMAP
118 load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
119 load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
120 load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
122 pp->font1 = load_texture_font (mi->dpy, "titleFont");
123 pp->font2 = load_texture_font (mi->dpy, "titleFont2");
124 pp->font3 = load_texture_font (mi->dpy, "titleFont3");
130 static void rpm_string (double rpm, char *s);
133 new_label (ModeInfo *mi)
135 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
140 if (pp->mouse_gear_id)
141 for (i = 0; i < pp->ngears; i++)
142 if (pp->gears[i]->id == pp->mouse_gear_id)
152 sprintf (label, "%d teeth\n", (int) g->nteeth);
153 rpm_string (g->rpm, label + strlen(label));
155 sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
157 (g->size == INVOLUTE_SMALL ? "small" :
158 g->size == INVOLUTE_MEDIUM ? "medium"
160 g->tooth_h * MI_HEIGHT(mi));
163 glNewList (pp->title_list, GL_COMPILE);
166 # ifdef HAVE_GLBITMAP
170 texture_font_data *fd;
172 if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
173 # ifdef HAVE_GLBITMAP
174 f = pp->xfont1, fl = pp->font1_dlist; /* big font */
178 else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
179 # ifdef HAVE_GLBITMAP
180 f = pp->xfont2, fl = pp->font2_dlist; /* small font */
185 # ifdef HAVE_GLBITMAP
186 f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
191 glColor3f (0.8, 0.8, 0);
192 print_gl_string (mi->dpy,
193 # ifdef HAVE_GLBITMAP
198 mi->xgwa.width, mi->xgwa.height,
199 10, mi->xgwa.height - 10,
210 /* Find the gear in the scene that is farthest to the right or left.
213 farthest_gear (ModeInfo *mi, Bool left_p)
215 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
218 double x = (left_p ? 999999 : -999999);
219 for (i = 0; i < pp->ngears; i++)
221 gear *g = pp->gears[i];
222 double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
223 if (left_p ? (x > gx) : (x < gx))
233 /* Compute the revolutions per minute of a gear.
236 compute_rpm (ModeInfo *mi, gear *g)
238 double fps, rpf, rps;
239 fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
241 if (fps > 150) fps = 150; /* let's be reasonable... */
242 if (fps < 10) fps = 10;
244 rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
245 rps = rpf * fps; /* rotations per second */
249 /* Prints the RPM into a string, doing fancy float formatting.
252 rpm_string (double rpm, char *s)
256 if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
257 else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
258 else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
259 else sprintf (buf, "%.16f",rpm);
262 while (buf[L-1] == '0') buf[--L] = 0;
263 if (buf[L-1] == '.') buf[--L] = 0;
274 /* Create and return a new gear sized for placement next to or on top of
275 the given parent gear (if any.) Returns 0 if out of memory.
278 new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
280 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
281 gear *g = (gear *) calloc (1, sizeof (*g));
283 static unsigned long id = 0; /* only used in debugging output */
286 if (coaxial_p && !parent) abort();
289 g->coax_displacement = pp->plane_displacement;
294 if (loop_count > 1000)
295 /* The only time we loop in here is when making a coaxial gear, and
296 trying to pick a radius that is either significantly larger or
297 smaller than its parent. That shouldn't be hard, so something
298 must be really wrong if we can't do that in only a few tries.
302 /* Pick the size of the teeth.
304 if (parent && !coaxial_p) /* adjascent gears need matching teeth */
306 g->tooth_w = parent->tooth_w;
307 g->tooth_h = parent->tooth_h;
308 g->thickness = parent->thickness;
309 g->thickness2 = parent->thickness2;
310 g->thickness3 = parent->thickness3;
312 else /* gears that begin trains get any size they want */
314 double scale = (1.0 + BELLRAND(4.0)) * gear_size;
315 g->tooth_w = 0.007 * scale;
316 g->tooth_h = 0.005 * scale;
317 g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
318 g->thickness2 = g->thickness / 4;
319 g->thickness3 = g->thickness;
322 /* Pick the number of teeth, and thus, the radius.
328 g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
330 if (g->nteeth < 7 && (random() % 5) != 0)
331 goto AGAIN; /* Let's make very small tooth-counts more rare */
333 c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
334 g->r = c / (M_PI * 2); /* c = 2 pi r */
340 if (! coaxial_p) break; /* yes */
341 if (g->nteeth == parent->nteeth) continue; /* ugly */
342 if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
343 if (parent->r < g->r * 0.6) break; /* g much larger than parent */
346 /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */
350 g->color[0] = 0.5 + frand(0.5);
351 g->color[1] = 0.5 + frand(0.5);
352 g->color[2] = 0.5 + frand(0.5);
355 g->color2[0] = g->color[0] * 0.85;
356 g->color2[1] = g->color[1] * 0.85;
357 g->color2[2] = g->color[2] * 0.85;
358 g->color2[3] = g->color[3];
361 /* Decide on shape of gear interior:
362 - just a ring with teeth;
363 - that, plus a thinner in-set "plate" in the middle;
364 - that, plus a thin raised "lip" on the inner plate;
365 - or, a wide lip (really, a thicker third inner plate.)
367 if ((random() % 10) == 0)
369 /* inner_r can go all the way in; there's no inset disc. */
370 g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
376 /* inner_r doesn't go in very far; inner_r2 is an inset disc. */
377 g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
378 g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
381 if (g->inner_r2 > (g->r * 0.2))
383 int nn = (random() % 10);
385 g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
386 else if (nn <= 7 && g->inner_r2 >= 0.1)
387 g->inner_r3 = g->inner_r2 - 0.01;
391 /* Coaxial gears need to have the same innermost hole size (for the axle.)
392 Use whichever of the two is smaller. (Modifies parent.)
396 double hole1 = (g->inner_r3 ? g->inner_r3 :
397 g->inner_r2 ? g->inner_r2 :
399 double hole2 = (parent->inner_r3 ? parent->inner_r3 :
400 parent->inner_r2 ? parent->inner_r2 :
402 double hole = (hole1 < hole2 ? hole1 : hole2);
403 if (hole <= 0) abort();
405 if (g->inner_r3) g->inner_r3 = hole;
406 else if (g->inner_r2) g->inner_r2 = hole;
407 else g->inner_r = hole;
409 if (parent->inner_r3) parent->inner_r3 = hole;
410 else if (parent->inner_r2) parent->inner_r2 = hole;
411 else parent->inner_r = hole;
414 /* If we have three discs, sometimes make the middle disc be spokes.
416 if (g->inner_r3 && ((random() % 5) == 0))
418 g->spokes = 2 + BELLRAND (5);
419 g->spoke_thickness = 1 + frand(7.0);
420 if (g->spokes == 2 && g->spoke_thickness < 2)
421 g->spoke_thickness += 1;
424 /* Sometimes add little nubbly bits, if there is room.
429 involute_biggest_ring (g, 0, &size, 0);
430 if (size > g->r * 0.2 && (random() % 5) == 0)
432 g->nubs = 1 + (random() % 16);
433 if (g->nubs > 8) g->nubs = 1;
437 if (g->inner_r3 > g->inner_r2) abort();
438 if (g->inner_r2 > g->inner_r) abort();
439 if (g->inner_r > g->r) abort();
441 /* Decide how complex the polygon model should be.
444 double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
445 if (pix <= 2.5) g->size = INVOLUTE_SMALL;
446 else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM;
447 else g->size = INVOLUTE_LARGE;
456 /* Given a newly-created gear, place it next to its parent in the scene,
457 with its teeth meshed and the proper velocity. Returns False if it
458 didn't work. (Call this a bunch of times until either it works, or
459 you decide it's probably not going to.)
462 place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
464 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
466 /* If this gear takes up more than 1/3rd of the screen, it's no good.
468 if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
469 ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
471 if (verbose_p && debug_placement_p && 0)
473 "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
475 (g->r + g->tooth_h), gear_size,
476 pp->vp_width, pp->vp_height);
477 pp->debug_size_failures++;
481 /* Compute this gear's velocity.
485 g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
486 g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
490 g->ratio = parent->ratio; /* bound gears have the same ratio */
492 g->rpm = parent->rpm;
493 g->wobble = parent->wobble;
497 /* Gearing ratio is the ratio of the number of teeth to previous gear
498 (which is also the ratio of the circumferences.)
500 g->ratio = (double) parent->nteeth / (double) g->nteeth;
502 /* Set our initial rotation to match that of the previous gear,
503 multiplied by the gearing ratio. (This is finessed later,
504 once we know the exact position of the gear relative to its
507 g->th = -(parent->th * g->ratio);
509 if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
511 double off = (180.0 / g->nteeth);
518 /* ratios are cumulative for all gears in the train. */
519 g->ratio *= parent->ratio;
523 /* Place the gear relative to the parent.
528 gear *rg = farthest_gear (mi, False);
529 double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
530 if (right < pp->layout_left) /* place off screen */
531 right = pp->layout_left;
533 g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
537 if (debug_one_gear_p)
542 double off = pp->plane_displacement;
546 g->z = parent->z + (g->r > parent->r /* small gear on top */
549 if (parent->r > g->r) /* mark which is top and which is bottom */
553 parent->wobble = 0; /* looks bad when axle moves */
562 g->coax_thickness = parent->thickness;
563 parent->coax_thickness = g->thickness;
565 /* Don't let the train get too close to or far from the screen.
566 If we're getting too close, give up on this gear.
567 (But getting very far away is fine.)
569 if (g->z >= off * 4 ||
572 if (verbose_p && debug_placement_p)
573 fprintf (stderr, "%s: placement: bad depth: %.2f\n",
575 pp->debug_position_failures++;
579 else /* position it somewhere next to the parent. */
581 double r_off = parent->r + g->r;
584 if ((random() % 3) != 0)
585 angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
587 angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
589 g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
590 g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
593 /* If the angle we picked would have positioned this gear
594 more than halfway off screen, that's no good. */
595 if (g->y > pp->vp_top ||
596 g->y < pp->vp_bottom)
598 if (verbose_p && debug_placement_p)
599 fprintf (stderr, "%s: placement: out of bounds: %s\n",
600 progname, (g->y > pp->vp_top ? "top" : "bottom"));
601 pp->debug_position_failures++;
605 /* avoid accidentally changing sign of "th" in the math below. */
606 g->th += (g->th > 0 ? 360 : -360);
608 /* Adjust the rotation of the gear so that its teeth line up with its
609 parent, based on the position of the gear and the current rotation
613 double p_c = 2 * M_PI * parent->r; /* circumference of parent */
614 double g_c = 2 * M_PI * g->r; /* circumference of g */
616 double p_t = p_c * (angle/360.0); /* distance travelled along
617 circumference of parent when
618 moving "angle" degrees along
620 double g_rat = p_t / g_c; /* if travelling that distance
621 along circumference of g,
622 ratio of g's circumference
624 double g_th = 360.0 * g_rat; /* that ratio in degrees */
626 g->th += angle + g_th;
630 if (debug_one_gear_p)
636 /* If the position we picked for this gear would cause it to already
637 be visible on the screen, give up. This can happen when the train
638 is growing backwards, and we don't want to see gears flash into
641 if (g->x - g->r - g->tooth_h < pp->render_right)
643 if (verbose_p && debug_placement_p)
644 fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
645 pp->debug_position_failures++;
649 /* If the position we picked for this gear causes it to overlap
650 with any earlier gear in the train, give up.
655 for (i = pp->ngears-1; i >= 0; i--)
657 gear *og = pp->gears[i];
659 if (og == g) continue;
660 if (og == parent) continue;
661 if (g->z != og->z) continue; /* Ignore unless on same layer */
663 /* Collision detection without sqrt:
664 d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
665 d < r1 + r2 d^2 < (r1 + r2)^2
667 if (((g->x - og->x) * (g->x - og->x) +
668 (g->y - og->y) * (g->y - og->y)) <
669 ((g->r + g->tooth_h + og->r + og->tooth_h) *
670 (g->r + g->tooth_h + og->r + og->tooth_h)))
672 if (verbose_p && debug_placement_p)
673 fprintf (stderr, "%s: placement: collision with %lu\n",
675 pp->debug_position_failures++;
684 /* Make deeper gears be darker.
687 double depth = g->z / pp->plane_displacement;
688 double brightness = 1 + (depth / 6);
690 if (brightness < limit) brightness = limit;
691 if (brightness > 1/limit) brightness = 1/limit;
692 g->color[0] *= brightness;
693 g->color[1] *= brightness;
694 g->color[2] *= brightness;
695 g->color2[0] *= brightness;
696 g->color2[1] *= brightness;
697 g->color2[2] *= brightness;
700 /* If a single frame of animation would cause the gear to rotate by
701 more than 1/2 the size of a single tooth, then it won't look right:
702 the gear will appear to be turning at some lower harmonic of its
706 double ratio = g->ratio * spin_speed;
707 double blur_limit = 180.0 / g->nteeth;
709 if (ratio > blur_limit)
710 g->motion_blur_p = 1;
714 /* ride until the wheels fall off... */
715 if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
716 if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
717 if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
718 if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
719 if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
720 if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
731 glDeleteLists (g->dlist, 1);
736 /* Make a new gear, place it next to its parent in the scene,
737 with its teeth meshed and the proper velocity. Returns the gear;
738 or 0 if it didn't work. (Call this a bunch of times until either
739 it works, or you decide it's probably not going to.)
742 place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
744 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
751 if (loop_count >= 100)
759 g = new_gear (mi, parent, coaxial_p);
760 if (!g) return 0; /* out of memory? */
762 if (place_gear (mi, g, parent, coaxial_p))
768 /* We got a gear, and it is properly positioned.
769 Insert it in the scene.
771 if (pp->ngears + 2 >= pp->gears_size)
773 pp->gears_size += 100;
774 pp->gears = (gear **) realloc (pp->gears,
775 pp->gears_size * sizeof (*pp->gears));
778 fprintf (stderr, "%s: out of memory (%d gears)\n",
779 progname, pp->gears_size);
783 pp->gears[pp->ngears++] = g;
788 static void delete_gear (ModeInfo *mi, gear *g);
791 push_gear (ModeInfo *mi)
793 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
795 gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
797 Bool tried_coaxial_p = False;
798 Bool coaxial_p = False;
799 Bool last_ditch_coax_p = False;
802 pp->debug_size_failures = 0;
803 pp->debug_position_failures = 0;
807 if (loop_count > 100) abort(); /* we're doomed! */
811 /* If the gears are turning at LUDICROUS SPEED, unhook the train to
812 reset things to a sane velocity.
814 10,000 RPM at 30 FPS = 5.5 rotations per frame.
815 1,000 RPM at 30 FPS = 0.5 rotations per frame.
816 600 RPM at 30 FPS = 3 frames per rotation.
817 200 RPM at 30 FPS = 9 frames per rotation.
818 100 RPM at 30 FPS = 18 frames per rotation.
819 50 RPM at 30 FPS = 36 frames per rotation.
820 10 RPM at 30 FPS = 3 sec per rotation.
821 1 RPM at 30 FPS = 30 sec per rotation.
822 .5 RPM at 30 FPS = 1 min per rotation.
823 .1 RPM at 30 FPS = 5 min per rotation.
825 if (parent && parent->rpm > max_rpm)
830 rpm_string (parent->rpm, buf);
831 fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
836 /* If the last N gears we've placed have all been motion-blurred, then
837 it's a safe guess that we've wandered off into the woods and aren't
838 coming back. Bail on this train.
840 if (pp->current_blur_length >= 10)
843 fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
849 /* Sometimes, try to make a coaxial gear.
851 if (parent && !parent->coax_p && (random() % 40) == 0)
853 tried_coaxial_p = True;
855 g = place_new_gear (mi, parent, coaxial_p);
858 /* Try to make a regular gear.
863 g = place_new_gear (mi, parent, coaxial_p);
866 /* If we couldn't make a regular gear, then try to make a coxial gear
867 (unless we already tried that.)
869 if (!g && !tried_coaxial_p && parent && !parent->coax_p)
871 tried_coaxial_p = True;
873 g = place_new_gear (mi, parent, coaxial_p);
875 last_ditch_coax_p = True;
878 /* If we couldn't do that either, then the train has hit a dead end:
885 fprintf (stderr, "%s: dead end!\n\n", progname);
887 g = place_new_gear (mi, parent, coaxial_p);
892 /* Unable to make/place any gears at all!
893 This can happen if we've backed ourself into a corner very near
894 the top-right or bottom-right corner of the growth zone.
895 It's time to add a gear, but there's no room to add one!
896 In that case, let's just wipe all the gears that are in the
897 growth zone and try again.
901 if (verbose_p && debug_placement_p)
903 "%s: placement: resetting growth zone! "
904 "failed: %d size, %d pos\n",
906 pp->debug_size_failures, pp->debug_position_failures);
907 for (i = pp->ngears-1; i >= 0; i--)
909 gear *g = pp->gears[i];
910 if (g->x - g->r - g->tooth_h < pp->render_left)
918 if (!parent) abort();
919 if (g->x != parent->x) abort();
920 if (g->y != parent->y) abort();
921 if (g->z == parent->z) abort();
922 if (g->r == parent->r) abort();
923 if (g->th != parent->th) abort();
924 if (g->ratio != parent->ratio) abort();
925 if (g->rpm != parent->rpm) abort();
930 fprintf (stderr, "%s: %5lu ", progname, g->id);
932 fputc ((g->motion_blur_p ? '*' : ' '), stderr);
933 fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
934 g->coax_p ? '1' : ' '),
937 fprintf (stderr, " %2d%%",
938 (int) (g->r * 2 * 100 / pp->vp_height));
939 fprintf (stderr, " %2d teeth", (int) g->nteeth);
940 fprintf (stderr, " %3.0f rpm;", g->rpm);
943 char buf1[50], buf2[50], buf3[100];
944 *buf1 = 0; *buf2 = 0; *buf3 = 0;
945 if (pp->debug_size_failures)
946 sprintf (buf1, "%3d sz", pp->debug_size_failures);
947 if (pp->debug_position_failures)
948 sprintf (buf2, "%2d pos", pp->debug_position_failures);
950 sprintf (buf3, " tries: %-7s%s", buf1, buf2);
951 fprintf (stderr, "%-21s", buf3);
954 if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length);
955 fprintf (stderr, "\n");
959 pp->current_length = 1;
961 pp->current_length++;
963 if (g->motion_blur_p)
964 pp->current_blur_length++;
966 pp->current_blur_length = 0;
971 /* Remove the given gear from the scene and free it.
974 delete_gear (ModeInfo *mi, gear *g)
976 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
979 for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
980 if (pp->gears[i] == g) break;
981 if (pp->gears[i] != g) abort();
983 for (; i < pp->ngears-1; i++) /* pull later entries forward */
984 pp->gears[i] = pp->gears[i+1];
991 /* Update the position of each gear in the scene.
994 scroll_gears (ModeInfo *mi)
996 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
999 for (i = 0; i < pp->ngears; i++)
1000 pp->gears[i]->x -= (scroll_speed * 0.002);
1002 /* if the right edge of any gear is off screen to the left, delete it.
1004 for (i = pp->ngears-1; i >= 0; i--)
1006 gear *g = pp->gears[i];
1007 if (g->x + g->r + g->tooth_h < pp->render_left)
1008 delete_gear (mi, g);
1011 /* if the right edge of the last-added gear is left of the right edge
1012 of the layout area, add another gear.
1017 gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1018 if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1023 if (debug_one_gear_p) break;
1027 if (i > 1 && verbose_p)
1028 fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1033 /* Update the rotation of each gear in the scene.
1036 spin_gears (ModeInfo *mi)
1038 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1041 for (i = 0; i < pp->ngears; i++)
1043 gear *g = pp->gears[i];
1044 double off = (g->ratio * spin_speed);
1054 /* Run the animation fast (without displaying anything) until the first
1055 gear is just about to come on screen. This is to avoid a big delay
1056 with a blank screen when -scroll is low.
1061 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1062 if (debug_one_gear_p) return;
1065 gear *g = farthest_gear (mi, True);
1066 if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1074 /* Render one gear in the proper position, creating the gear's
1075 display list first if necessary.
1078 draw_gear (ModeInfo *mi, int which)
1080 Bool wire_p = MI_IS_WIREFRAME(mi);
1081 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1082 gear *g = pp->gears[which];
1085 Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1086 g->x - g->r - g->tooth_h <= pp->render_right);
1088 if (!visible_p && !debug_p)
1093 g->dlist = glGenLists (1);
1096 /* I don't know how many display lists a GL implementation
1097 is supposed to provide, but hopefully it's more than
1098 "a few hundred", or we'll be in trouble...
1100 check_gl_error ("glGenLists");
1104 glNewList (g->dlist, GL_COMPILE);
1105 g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p));
1111 glTranslatef (g->x, g->y, g->z);
1113 if (g->motion_blur_p && !pp->button_down_p)
1115 /* If we're in motion-blur mode, then draw the gear so that each
1116 frame rotates it by exactly one half tooth-width, so that it
1117 looks flickery around the edges. But, revert to the normal
1118 way when the mouse button is down lest the user see overlapping
1121 th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1127 glRotatef (th, 0, 0, 1);
1132 mi->polygon_count += draw_involute_schematic (g, wire_p);
1135 glCallList (g->dlist);
1136 mi->polygon_count += g->polygons;
1144 /* Render all gears.
1147 draw_gears (ModeInfo *mi)
1149 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1150 Bool wire_p = MI_IS_WIREFRAME(mi);
1153 glColor4f (1, 1, 0.8, 1);
1157 for (i = 0; i < pp->ngears; i++)
1160 /* draw a line connecting gears that are, uh, geared. */
1163 static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
1165 GLfloat ox=0, oy=0, oz=0;
1167 if (!wire_p) glDisable(GL_LIGHTING);
1168 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
1169 glColor3f (color[0], color[1], color[2]);
1171 for (i = 0; i < pp->ngears; i++)
1173 gear *g = pp->gears[i];
1174 glBegin(GL_LINE_STRIP);
1175 glVertex3f (g->x, g->y, g->z - off);
1176 glVertex3f (g->x, g->y, g->z + off);
1177 if (i > 0 && !g->base_p)
1178 glVertex3f (ox, oy, oz + off);
1184 if (!wire_p) glEnable(GL_LIGHTING);
1189 /* Mouse hit detection
1192 find_mouse_gear (ModeInfo *mi)
1194 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1196 # ifndef HAVE_JWZGLES
1198 int screen_width = MI_WIDTH (mi);
1199 int screen_height = MI_HEIGHT (mi);
1200 GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
1204 pp->mouse_gear_id = 0;
1206 /* Poll mouse position */
1211 XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
1212 &r, &c, &rx, &ry, &x, &y, &m);
1215 if (x < 0 || y < 0 || x > screen_width || y > screen_height)
1216 return; /* out of window */
1218 /* Run OpenGL hit detector */
1223 glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
1224 glRenderMode (GL_SELECT);
1225 glMatrixMode (GL_PROJECTION);
1228 glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
1229 gluPickMatrix (x, vp[3]-y, 5, 5, vp);
1230 gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
1231 glMatrixMode (GL_MODELVIEW);
1233 draw_gears (mi); /* render into "select" buffer */
1235 glMatrixMode (GL_PROJECTION); /* restore old vp */
1237 glMatrixMode (GL_MODELVIEW);
1239 hits = glRenderMode (GL_RENDER); /* done selecting */
1244 GLuint name_count = 0;
1245 GLuint *p = (GLuint *) selbuf;
1249 for (i = 0; i < hits; i++)
1252 if (*p < min_z) /* find match closest to screen */
1261 if (name_count > 0) /* take first hit */
1262 pp->mouse_gear_id = pnames[0];
1266 #else /* HAVE_JWZGLES */
1267 /* #### not yet implemented */
1268 pp->mouse_gear_id = pp->gears[1]->id;
1270 #endif /* HAVE_JWZGLES */
1276 /* Window management, etc
1279 reshape_pinion (ModeInfo *mi, int width, int height)
1281 GLfloat h = (GLfloat) height / (GLfloat) width;
1282 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1284 glViewport (0, 0, (GLint) width, (GLint) height);
1286 glMatrixMode(GL_PROJECTION);
1288 gluPerspective (30.0, 1/h, 1.0, 100.0);
1290 glMatrixMode(GL_MODELVIEW);
1292 gluLookAt( 0.0, 0.0, 30.0,
1296 glClear(GL_COLOR_BUFFER_BIT);
1299 GLfloat render_width, layout_width;
1300 pp->vp_height = 1.0;
1303 pp->vp_left = -pp->vp_width/2;
1304 pp->vp_right = pp->vp_width/2;
1305 pp->vp_top = pp->vp_height/2;
1306 pp->vp_bottom = -pp->vp_height/2;
1308 render_width = pp->vp_width * 2;
1309 layout_width = pp->vp_width * 0.8 * gear_size;
1311 pp->render_left = -render_width/2;
1312 pp->render_right = render_width/2;
1314 pp->layout_left = pp->render_right;
1315 pp->layout_right = pp->layout_left + layout_width;
1321 pinion_handle_event (ModeInfo *mi, XEvent *event)
1323 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1325 if (event->xany.type == ButtonPress &&
1326 event->xbutton.button == Button1)
1328 pp->button_down_p = True;
1329 gltrackball_start (pp->trackball,
1330 event->xbutton.x, event->xbutton.y,
1331 MI_WIDTH (mi), MI_HEIGHT (mi));
1334 else if (event->xany.type == ButtonRelease &&
1335 event->xbutton.button == Button1)
1337 pp->button_down_p = False;
1340 else if (event->xany.type == ButtonPress &&
1341 (event->xbutton.button == Button4 ||
1342 event->xbutton.button == Button5 ||
1343 event->xbutton.button == Button6 ||
1344 event->xbutton.button == Button7))
1346 gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
1347 !!event->xbutton.state);
1350 else if (event->xany.type == MotionNotify &&
1353 gltrackball_track (pp->trackball,
1354 event->xmotion.x, event->xmotion.y,
1355 MI_WIDTH (mi), MI_HEIGHT (mi));
1358 else if (event->xany.type == KeyPress)
1362 XLookupString (&event->xkey, &c, 1, &keysym, 0);
1363 if (c == ' ' && debug_one_gear_p && pp->ngears)
1365 delete_gear (mi, pp->gears[0]);
1375 init_pinion (ModeInfo *mi)
1377 pinion_configuration *pp;
1380 pps = (pinion_configuration *)
1381 calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
1383 fprintf(stderr, "%s: out of memory\n", progname);
1388 pp = &pps[MI_SCREEN(mi)];
1390 pp->glx_context = init_GL(mi);
1393 reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1394 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
1396 pp->title_list = glGenLists (1);
1402 pp->plane_displacement = gear_size * 0.1;
1404 pp->trackball = gltrackball_init ();
1411 draw_pinion (ModeInfo *mi)
1413 pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1414 Display *dpy = MI_DISPLAY(mi);
1415 Window window = MI_WINDOW(mi);
1416 Bool wire_p = MI_IS_WIREFRAME(mi);
1418 if (!pp->glx_context)
1421 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pp->glx_context));
1424 glRotatef(current_device_rotation(), 0, 0, 1);
1428 GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
1429 GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
1430 GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
1431 GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
1433 glEnable(GL_LIGHTING);
1434 glEnable(GL_LIGHT0);
1435 glEnable(GL_DEPTH_TEST);
1436 glEnable(GL_CULL_FACE);
1438 glLightfv(GL_LIGHT0, GL_POSITION, pos);
1439 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1440 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1441 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
1444 if (!pp->button_down_p)
1446 if (!debug_one_gear_p || pp->ngears == 0)
1451 glShadeModel(GL_SMOOTH);
1453 glEnable(GL_DEPTH_TEST);
1454 glEnable(GL_NORMALIZE);
1455 glEnable(GL_CULL_FACE);
1457 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1461 gltrackball_rotate (pp->trackball);
1462 mi->polygon_count = 0;
1464 glScalef (16, 16, 16); /* map vp_width/height to the screen */
1466 if (debug_one_gear_p) /* zoom in */
1468 else if (debug_p) /* show the "visible" and "layout" areas */
1470 GLfloat ow = pp->layout_right - pp->render_left;
1471 GLfloat rw = pp->render_right - pp->render_left;
1472 GLfloat s = (pp->vp_width / ow) * 0.85;
1474 glTranslatef (-(ow - rw) / 2, 0, 0);
1479 glScalef (s, s, s); /* zoom in a little more */
1480 glRotatef (-35, 1, 0, 0); /* tilt back */
1481 glRotatef ( 8, 0, 1, 0); /* tilt left */
1482 glTranslatef (0.02, 0.1, 0); /* pan up */
1489 if (!wire_p) glDisable(GL_LIGHTING);
1490 glColor3f (0.6, 0, 0);
1491 glBegin(GL_LINE_LOOP);
1492 glVertex3f (pp->render_left, pp->vp_top, 0);
1493 glVertex3f (pp->render_right, pp->vp_top, 0);
1494 glVertex3f (pp->render_right, pp->vp_bottom, 0);
1495 glVertex3f (pp->render_left, pp->vp_bottom, 0);
1497 glColor3f (0.4, 0, 0);
1499 glVertex3f (pp->vp_left, pp->vp_top, 0);
1500 glVertex3f (pp->vp_left, pp->vp_bottom, 0);
1501 glVertex3f (pp->vp_right, pp->vp_top, 0);
1502 glVertex3f (pp->vp_right, pp->vp_bottom, 0);
1504 glColor3f (0, 0.4, 0);
1505 glBegin(GL_LINE_LOOP);
1506 glVertex3f (pp->layout_left, pp->vp_top, 0);
1507 glVertex3f (pp->layout_right, pp->vp_top, 0);
1508 glVertex3f (pp->layout_right, pp->vp_bottom, 0);
1509 glVertex3f (pp->layout_left, pp->vp_bottom, 0);
1511 if (!wire_p) glEnable(GL_LIGHTING);
1514 if (pp->draw_tick++ > 10) /* only do this every N frames */
1517 find_mouse_gear (mi);
1523 glCallList (pp->title_list);
1526 if (mi->fps_p) do_fps (mi);
1529 glXSwapBuffers(dpy, window);
1532 XSCREENSAVER_MODULE ("Pinion", pinion)