/* pinion, Copyright (c) 2004-2014 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ #define DEFAULTS "*delay: 15000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*titleFont: -*-helvetica-medium-r-normal-*-*-180-*-*-*-*-*-*\n" \ "*titleFont2: -*-helvetica-medium-r-normal-*-*-120-*-*-*-*-*-*\n" \ "*titleFont3: -*-helvetica-medium-r-normal-*-*-80-*-*-*-*-*-*\n" \ # define release_pinion 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #undef BELLRAND #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3) #include "xlockmore.h" #include "normals.h" #include "gltrackball.h" #include "texfont.h" #include "involute.h" #include #ifdef USE_GL /* whole file */ #define DEF_SPIN_SPEED "1.0" #define DEF_SCROLL_SPEED "1.0" #define DEF_GEAR_SIZE "1.0" #define DEF_MAX_RPM "900" typedef struct { GLXContext *glx_context; GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */ GLfloat vp_width, vp_height; GLfloat render_left, render_right; /* area in which gears are displayed */ GLfloat layout_left, layout_right; /* layout region, on the right side */ int ngears; int gears_size; gear **gears; trackball_state *trackball; Bool button_down_p; unsigned long mouse_gear_id; texture_font_data *font1, *font2, *font3; int draw_tick; GLfloat plane_displacement; /* distance between coaxial gears */ int debug_size_failures; /* for debugging messages */ int debug_position_failures; unsigned long current_length; /* gear count in current train */ unsigned long current_blur_length; /* how long have we been blurring? */ } pinion_configuration; static pinion_configuration *pps = NULL; /* command line arguments */ static GLfloat spin_speed, scroll_speed, gear_size, max_rpm; static Bool verbose_p = False; /* print progress on stderr */ static Bool debug_p = False; /* render as flat schematic */ /* internal debugging variables */ static Bool debug_placement_p = False; /* extreme verbosity on stderr */ static Bool debug_one_gear_p = False; /* draw one big stationary gear */ static XrmOptionDescRec opts[] = { { "-spin", ".spinSpeed", XrmoptionSepArg, 0 }, { "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 }, { "-size", ".gearSize", XrmoptionSepArg, 0 }, { "-max-rpm",".maxRPM", XrmoptionSepArg, 0 }, { "-debug", ".debug", XrmoptionNoArg, "True" }, { "-verbose",".verbose", XrmoptionNoArg, "True" }, }; static argtype vars[] = { {&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float}, {&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float}, {&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float}, {&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float}, {&debug_p, "debug", "Debug", "False", t_Bool}, {&verbose_p, "verbose", "Verbose", "False", t_Bool}, }; ENTRYPOINT ModeSpecOpt pinion_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Font stuff */ static void load_fonts (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; pp->font1 = load_texture_font (mi->dpy, "titleFont"); pp->font2 = load_texture_font (mi->dpy, "titleFont2"); pp->font3 = load_texture_font (mi->dpy, "titleFont3"); } static void rpm_string (double rpm, char *s); static void draw_label (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; char label[1024]; int i; gear *g = 0; if (pp->mouse_gear_id) for (i = 0; i < pp->ngears; i++) if (pp->gears[i]->id == pp->mouse_gear_id) { g = pp->gears[i]; break; } if (!g) *label = 0; else { sprintf (label, "%d teeth\n", (int) g->nteeth); rpm_string (g->rpm, label + strlen(label)); if (debug_p) sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n", g->polygons, (g->size == INVOLUTE_SMALL ? "small" : g->size == INVOLUTE_MEDIUM ? "medium" : "large"), g->tooth_h * MI_HEIGHT(mi)); } if (*label) { texture_font_data *fd; if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375) fd = pp->font1; else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260) fd = pp->font2; else fd = pp->font3; glColor3f (0.8, 0.8, 0); print_texture_label (mi->dpy, fd, mi->xgwa.width, mi->xgwa.height, 1, label); } } /* Some utilities */ /* Find the gear in the scene that is farthest to the right or left. */ static gear * farthest_gear (ModeInfo *mi, Bool left_p) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int i; gear *rg = 0; double x = (left_p ? 999999 : -999999); for (i = 0; i < pp->ngears; i++) { gear *g = pp->gears[i]; double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1)); if (left_p ? (x > gx) : (x < gx)) { rg = g; x = gx; } } return rg; } /* Compute the revolutions per minute of a gear. */ static void compute_rpm (ModeInfo *mi, gear *g) { double fps, rpf, rps; fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi)); if (fps > 150) fps = 150; /* let's be reasonable... */ if (fps < 10) fps = 10; rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */ rps = rpf * fps; /* rotations per second */ g->rpm = rps * 60; } /* Prints the RPM into a string, doing fancy float formatting. */ static void rpm_string (double rpm, char *s) { char buf[30]; int L; if (rpm >= 0.1) sprintf (buf, "%.2f", rpm); else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm); else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm); else sprintf (buf, "%.16f",rpm); L = strlen(buf); while (buf[L-1] == '0') buf[--L] = 0; if (buf[L-1] == '.') buf[--L] = 0; strcpy (s, buf); strcat (s, " RPM"); } /* Layout and stuff. */ /* Create and return a new gear sized for placement next to or on top of the given parent gear (if any.) Returns 0 if out of memory. */ static gear * new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; gear *g = (gear *) calloc (1, sizeof (*g)); int loop_count = 0; static unsigned long id = 0; /* only used in debugging output */ if (!g) return 0; if (coaxial_p && !parent) abort(); g->id = ++id; g->coax_displacement = pp->plane_displacement; while (1) { loop_count++; if (loop_count > 1000) /* The only time we loop in here is when making a coaxial gear, and trying to pick a radius that is either significantly larger or smaller than its parent. That shouldn't be hard, so something must be really wrong if we can't do that in only a few tries. */ abort(); /* Pick the size of the teeth. */ if (parent && !coaxial_p) /* adjascent gears need matching teeth */ { g->tooth_w = parent->tooth_w; g->tooth_h = parent->tooth_h; g->thickness = parent->thickness; g->thickness2 = parent->thickness2; g->thickness3 = parent->thickness3; } else /* gears that begin trains get any size they want */ { double scale = (1.0 + BELLRAND(4.0)) * gear_size; g->tooth_w = 0.007 * scale; g->tooth_h = 0.005 * scale; g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5)); g->thickness2 = g->thickness / 4; g->thickness3 = g->thickness; } /* Pick the number of teeth, and thus, the radius. */ { double c; AGAIN: g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */ if (g->nteeth < 7 && (random() % 5) != 0) goto AGAIN; /* Let's make very small tooth-counts more rare */ c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */ g->r = c / (M_PI * 2); /* c = 2 pi r */ } /* Are we done now? */ if (! coaxial_p) break; /* yes */ if (g->nteeth == parent->nteeth) continue; /* ugly */ if (g->r < parent->r * 0.6) break; /* g much smaller than parent */ if (parent->r < g->r * 0.6) break; /* g much larger than parent */ } /* g->tooth_slope = (parent ? -parent->tooth_slope : 4); */ if (debug_one_gear_p) g->tooth_slope = frand(20)-10; /* Colorize */ g->color[0] = 0.5 + frand(0.5); g->color[1] = 0.5 + frand(0.5); g->color[2] = 0.5 + frand(0.5); g->color[3] = 1.0; g->color2[0] = g->color[0] * 0.85; g->color2[1] = g->color[1] * 0.85; g->color2[2] = g->color[2] * 0.85; g->color2[3] = g->color[3]; /* Decide on shape of gear interior: - just a ring with teeth; - that, plus a thinner in-set "plate" in the middle; - that, plus a thin raised "lip" on the inner plate; - or, a wide lip (really, a thicker third inner plate.) */ if ((random() % 10) == 0) { /* inner_r can go all the way in; there's no inset disc. */ g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8); g->inner_r2 = 0; g->inner_r3 = 0; } else { /* inner_r doesn't go in very far; inner_r2 is an inset disc. */ g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4); g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5); g->inner_r3 = 0; if (g->inner_r2 > (g->r * 0.2)) { int nn = (random() % 10); if (nn <= 2) g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2); else if (nn <= 7 && g->inner_r2 >= 0.1) g->inner_r3 = g->inner_r2 - 0.01; } } /* Coaxial gears need to have the same innermost hole size (for the axle.) Use whichever of the two is smaller. (Modifies parent.) */ if (coaxial_p) { double hole1 = (g->inner_r3 ? g->inner_r3 : g->inner_r2 ? g->inner_r2 : g->inner_r); double hole2 = (parent->inner_r3 ? parent->inner_r3 : parent->inner_r2 ? parent->inner_r2 : parent->inner_r); double hole = (hole1 < hole2 ? hole1 : hole2); if (hole <= 0) abort(); if (g->inner_r3) g->inner_r3 = hole; else if (g->inner_r2) g->inner_r2 = hole; else g->inner_r = hole; if (parent->inner_r3) parent->inner_r3 = hole; else if (parent->inner_r2) parent->inner_r2 = hole; else parent->inner_r = hole; } /* If we have three discs, sometimes make the middle disc be spokes. */ if (g->inner_r3 && ((random() % 5) == 0)) { g->spokes = 2 + BELLRAND (5); g->spoke_thickness = 1 + frand(7.0); if (g->spokes == 2 && g->spoke_thickness < 2) g->spoke_thickness += 1; } /* Sometimes add little nubbly bits, if there is room. */ if (g->nteeth > 5) { double size = 0; involute_biggest_ring (g, 0, &size, 0); if (size > g->r * 0.2 && (random() % 5) == 0) { g->nubs = 1 + (random() % 16); if (g->nubs > 8) g->nubs = 1; } } if (g->inner_r3 > g->inner_r2) abort(); if (g->inner_r2 > g->inner_r) abort(); if (g->inner_r > g->r) abort(); /* Decide how complex the polygon model should be. */ { double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */ if (pix <= 2.5) g->size = INVOLUTE_SMALL; else if (pix <= 3.5) g->size = INVOLUTE_MEDIUM; else if (pix <= 25) g->size = INVOLUTE_LARGE; else g->size = INVOLUTE_HUGE; } g->base_p = !parent; return g; } /* Given a newly-created gear, place it next to its parent in the scene, with its teeth meshed and the proper velocity. Returns False if it didn't work. (Call this a bunch of times until either it works, or you decide it's probably not going to.) */ static Bool place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; /* If this gear takes up more than 1/3rd of the screen, it's no good. */ if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width || ((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height) { if (verbose_p && debug_placement_p && 0) fprintf (stderr, "%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n", progname, (g->r + g->tooth_h), gear_size, pp->vp_width, pp->vp_height); pp->debug_size_failures++; return False; } /* Compute this gear's velocity. */ if (! parent) { g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */ g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0); } else if (coaxial_p) { g->ratio = parent->ratio; /* bound gears have the same ratio */ g->th = parent->th; g->rpm = parent->rpm; g->wobble = parent->wobble; } else { /* Gearing ratio is the ratio of the number of teeth to previous gear (which is also the ratio of the circumferences.) */ g->ratio = (double) parent->nteeth / (double) g->nteeth; /* Set our initial rotation to match that of the previous gear, multiplied by the gearing ratio. (This is finessed later, once we know the exact position of the gear relative to its parent.) */ g->th = -(parent->th * g->ratio); if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */ { double off = (180.0 / g->nteeth); if (g->th > 0) g->th += off; else g->th -= off; } /* ratios are cumulative for all gears in the train. */ g->ratio *= parent->ratio; } /* Place the gear relative to the parent. */ if (! parent) { gear *rg = farthest_gear (mi, False); double right = (rg ? rg->x + rg->r + rg->tooth_h : 0); if (right < pp->layout_left) /* place off screen */ right = pp->layout_left; g->x = right + g->r + g->tooth_h + (0.01 / gear_size); g->y = 0; g->z = 0; if (debug_one_gear_p) g->x = 0; } else if (coaxial_p) { double off = pp->plane_displacement; g->x = parent->x; g->y = parent->y; g->z = parent->z + (g->r > parent->r /* small gear on top */ ? -off : off); if (parent->r > g->r) /* mark which is top and which is bottom */ { parent->coax_p = 1; g->coax_p = 2; parent->wobble = 0; /* looks bad when axle moves */ } else { parent->coax_p = 2; g->coax_p = 1; g->wobble = 0; } g->coax_thickness = parent->thickness; parent->coax_thickness = g->thickness; /* Don't let the train get too close to or far from the screen. If we're getting too close, give up on this gear. (But getting very far away is fine.) */ if (g->z >= off * 4 || g->z <= -off * 4) { if (verbose_p && debug_placement_p) fprintf (stderr, "%s: placement: bad depth: %.2f\n", progname, g->z); pp->debug_position_failures++; return False; } } else /* position it somewhere next to the parent. */ { double r_off = parent->r + g->r; int angle; if ((random() % 3) != 0) angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */ else angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */ g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off); g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off); g->z = parent->z; /* If the angle we picked would have positioned this gear more than halfway off screen, that's no good. */ if (g->y > pp->vp_top || g->y < pp->vp_bottom) { if (verbose_p && debug_placement_p) fprintf (stderr, "%s: placement: out of bounds: %s\n", progname, (g->y > pp->vp_top ? "top" : "bottom")); pp->debug_position_failures++; return False; } /* avoid accidentally changing sign of "th" in the math below. */ g->th += (g->th > 0 ? 360 : -360); /* Adjust the rotation of the gear so that its teeth line up with its parent, based on the position of the gear and the current rotation of the parent. */ { double p_c = 2 * M_PI * parent->r; /* circumference of parent */ double g_c = 2 * M_PI * g->r; /* circumference of g */ double p_t = p_c * (angle/360.0); /* distance travelled along circumference of parent when moving "angle" degrees along parent. */ double g_rat = p_t / g_c; /* if travelling that distance along circumference of g, ratio of g's circumference travelled. */ double g_th = 360.0 * g_rat; /* that ratio in degrees */ g->th += angle + g_th; } } if (debug_one_gear_p) { compute_rpm (mi, g); return True; } /* If the position we picked for this gear would cause it to already be visible on the screen, give up. This can happen when the train is growing backwards, and we don't want to see gears flash into existence. */ if (g->x - g->r - g->tooth_h < pp->render_right) { if (verbose_p && debug_placement_p) fprintf (stderr, "%s: placement: out of bounds: left\n", progname); pp->debug_position_failures++; return False; } /* If the position we picked for this gear causes it to overlap with any earlier gear in the train, give up. */ { int i; for (i = pp->ngears-1; i >= 0; i--) { gear *og = pp->gears[i]; if (og == g) continue; if (og == parent) continue; if (g->z != og->z) continue; /* Ignore unless on same layer */ /* Collision detection without sqrt: d = sqrt(a^2 + b^2) d^2 = a^2 + b^2 d < r1 + r2 d^2 < (r1 + r2)^2 */ if (((g->x - og->x) * (g->x - og->x) + (g->y - og->y) * (g->y - og->y)) < ((g->r + g->tooth_h + og->r + og->tooth_h) * (g->r + g->tooth_h + og->r + og->tooth_h))) { if (verbose_p && debug_placement_p) fprintf (stderr, "%s: placement: collision with %lu\n", progname, og->id); pp->debug_position_failures++; return False; } } } compute_rpm (mi, g); /* Make deeper gears be darker. */ { double depth = g->z / pp->plane_displacement; double brightness = 1 + (depth / 6); double limit = 0.4; if (brightness < limit) brightness = limit; if (brightness > 1/limit) brightness = 1/limit; g->color[0] *= brightness; g->color[1] *= brightness; g->color[2] *= brightness; g->color2[0] *= brightness; g->color2[1] *= brightness; g->color2[2] *= brightness; } /* If a single frame of animation would cause the gear to rotate by more than 1/2 the size of a single tooth, then it won't look right: the gear will appear to be turning at some lower harmonic of its actual speed. */ { double ratio = g->ratio * spin_speed; double blur_limit = 180.0 / g->nteeth; if (ratio > blur_limit) g->motion_blur_p = 1; if (!coaxial_p) { /* ride until the wheels fall off... */ if (ratio > blur_limit * 0.7) g->wobble += (random() % 2); if (ratio > blur_limit * 0.9) g->wobble += (random() % 2); if (ratio > blur_limit * 1.1) g->wobble += (random() % 2); if (ratio > blur_limit * 1.3) g->wobble += (random() % 2); if (ratio > blur_limit * 1.5) g->wobble += (random() % 2); if (ratio > blur_limit * 1.7) g->wobble += (random() % 2); } } return True; } static void free_gear (gear *g) { if (g->dlist) glDeleteLists (g->dlist, 1); free (g); } /* Make a new gear, place it next to its parent in the scene, with its teeth meshed and the proper velocity. Returns the gear; or 0 if it didn't work. (Call this a bunch of times until either it works, or you decide it's probably not going to.) */ static gear * place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int loop_count = 0; gear *g = 0; while (1) { loop_count++; if (loop_count >= 100) { if (g) free_gear (g); g = 0; break; } g = new_gear (mi, parent, coaxial_p); if (!g) return 0; /* out of memory? */ if (place_gear (mi, g, parent, coaxial_p)) break; } if (! g) return 0; /* We got a gear, and it is properly positioned. Insert it in the scene. */ if (pp->ngears + 2 >= pp->gears_size) { pp->gears_size += 100; pp->gears = (gear **) realloc (pp->gears, pp->gears_size * sizeof (*pp->gears)); if (! pp->gears) { fprintf (stderr, "%s: out of memory (%d gears)\n", progname, pp->gears_size); } } pp->gears[pp->ngears++] = g; return g; } static void delete_gear (ModeInfo *mi, gear *g); static void push_gear (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; gear *g; gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]); Bool tried_coaxial_p = False; Bool coaxial_p = False; Bool last_ditch_coax_p = False; int loop_count = 0; pp->debug_size_failures = 0; pp->debug_position_failures = 0; AGAIN: loop_count++; if (loop_count > 100) abort(); /* we're doomed! */ g = 0; /* If the gears are turning at LUDICROUS SPEED, unhook the train to reset things to a sane velocity. 10,000 RPM at 30 FPS = 5.5 rotations per frame. 1,000 RPM at 30 FPS = 0.5 rotations per frame. 600 RPM at 30 FPS = 3 frames per rotation. 200 RPM at 30 FPS = 9 frames per rotation. 100 RPM at 30 FPS = 18 frames per rotation. 50 RPM at 30 FPS = 36 frames per rotation. 10 RPM at 30 FPS = 3 sec per rotation. 1 RPM at 30 FPS = 30 sec per rotation. .5 RPM at 30 FPS = 1 min per rotation. .1 RPM at 30 FPS = 5 min per rotation. */ if (parent && parent->rpm > max_rpm) { if (verbose_p) { char buf[100]; rpm_string (parent->rpm, buf); fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf); } parent = 0; } /* If the last N gears we've placed have all been motion-blurred, then it's a safe guess that we've wandered off into the woods and aren't coming back. Bail on this train. */ if (pp->current_blur_length >= 10) { if (verbose_p) fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname); parent = 0; } /* Sometimes, try to make a coaxial gear. */ if (parent && !parent->coax_p && (random() % 40) == 0) { tried_coaxial_p = True; coaxial_p = True; g = place_new_gear (mi, parent, coaxial_p); } /* Try to make a regular gear. */ if (!g) { coaxial_p = False; g = place_new_gear (mi, parent, coaxial_p); } /* If we couldn't make a regular gear, then try to make a coxial gear (unless we already tried that.) */ if (!g && !tried_coaxial_p && parent && !parent->coax_p) { tried_coaxial_p = True; coaxial_p = True; g = place_new_gear (mi, parent, coaxial_p); if (g) last_ditch_coax_p = True; } /* If we couldn't do that either, then the train has hit a dead end: start a new train. */ if (!g) { coaxial_p = False; if (verbose_p) fprintf (stderr, "%s: dead end!\n\n", progname); parent = 0; g = place_new_gear (mi, parent, coaxial_p); } if (! g) { /* Unable to make/place any gears at all! This can happen if we've backed ourself into a corner very near the top-right or bottom-right corner of the growth zone. It's time to add a gear, but there's no room to add one! In that case, let's just wipe all the gears that are in the growth zone and try again. */ int i; if (verbose_p && debug_placement_p) fprintf (stderr, "%s: placement: resetting growth zone! " "failed: %d size, %d pos\n", progname, pp->debug_size_failures, pp->debug_position_failures); for (i = pp->ngears-1; i >= 0; i--) { gear *g = pp->gears[i]; if (g->x - g->r - g->tooth_h < pp->render_left) delete_gear (mi, g); } goto AGAIN; } if (g->coax_p) { if (!parent) abort(); if (g->x != parent->x) abort(); if (g->y != parent->y) abort(); if (g->z == parent->z) abort(); if (g->r == parent->r) abort(); if (g->th != parent->th) abort(); if (g->ratio != parent->ratio) abort(); if (g->rpm != parent->rpm) abort(); } if (verbose_p) { fprintf (stderr, "%s: %5lu ", progname, g->id); fputc ((g->motion_blur_p ? '*' : ' '), stderr); fputc (((g->coax_p && last_ditch_coax_p) ? '2' : g->coax_p ? '1' : ' '), stderr); fprintf (stderr, " %2d%%", (int) (g->r * 2 * 100 / pp->vp_height)); fprintf (stderr, " %2d teeth", (int) g->nteeth); fprintf (stderr, " %3.0f rpm;", g->rpm); { char buf1[50], buf2[50], buf3[100]; *buf1 = 0; *buf2 = 0; *buf3 = 0; if (pp->debug_size_failures) sprintf (buf1, "%3d sz", pp->debug_size_failures); if (pp->debug_position_failures) sprintf (buf2, "%2d pos", pp->debug_position_failures); if (*buf1 || *buf2) sprintf (buf3, " tries: %-7s%s", buf1, buf2); fprintf (stderr, "%-21s", buf3); } if (g->base_p) fprintf (stderr, " RESET %lu", pp->current_length); fprintf (stderr, "\n"); } if (g->base_p) pp->current_length = 1; else pp->current_length++; if (g->motion_blur_p) pp->current_blur_length++; else pp->current_blur_length = 0; } /* Remove the given gear from the scene and free it. */ static void delete_gear (ModeInfo *mi, gear *g) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int i; for (i = 0; i < pp->ngears; i++) /* find this gear in the list */ if (pp->gears[i] == g) break; if (pp->gears[i] != g) abort(); for (; i < pp->ngears-1; i++) /* pull later entries forward */ pp->gears[i] = pp->gears[i+1]; pp->gears[i] = 0; pp->ngears--; free_gear (g); } /* Update the position of each gear in the scene. */ static void scroll_gears (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int i; for (i = 0; i < pp->ngears; i++) pp->gears[i]->x -= (scroll_speed * 0.002); /* if the right edge of any gear is off screen to the left, delete it. */ for (i = pp->ngears-1; i >= 0; i--) { gear *g = pp->gears[i]; if (g->x + g->r + g->tooth_h < pp->render_left) delete_gear (mi, g); } /* if the right edge of the last-added gear is left of the right edge of the layout area, add another gear. */ i = 0; while (1) { gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]); if (!g || g->x + g->r + g->tooth_h < pp->layout_right) push_gear (mi); else break; i++; if (debug_one_gear_p) break; } /* if (i > 1 && verbose_p) fprintf (stderr, "%s: pushed %d gears\n", progname, i); */ } /* Update the rotation of each gear in the scene. */ static void spin_gears (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int i; for (i = 0; i < pp->ngears; i++) { gear *g = pp->gears[i]; double off = (g->ratio * spin_speed); if (g->th > 0) g->th += off; else g->th -= off; } } /* Run the animation fast (without displaying anything) until the first gear is just about to come on screen. This is to avoid a big delay with a blank screen when -scroll is low. */ static void ffwd (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; if (debug_one_gear_p) return; while (1) { gear *g = farthest_gear (mi, True); if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88) break; scroll_gears (mi); } } /* Render one gear in the proper position, creating the gear's display list first if necessary. */ static void draw_gear (ModeInfo *mi, int which) { Bool wire_p = MI_IS_WIREFRAME(mi); pinion_configuration *pp = &pps[MI_SCREEN(mi)]; gear *g = pp->gears[which]; GLfloat th; Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left && g->x - g->r - g->tooth_h <= pp->render_right); if (!visible_p && !debug_p) return; if (! g->dlist) { g->dlist = glGenLists (1); if (! g->dlist) { /* I don't know how many display lists a GL implementation is supposed to provide, but hopefully it's more than "a few hundred", or we'll be in trouble... */ check_gl_error ("glGenLists"); abort(); } glNewList (g->dlist, GL_COMPILE); g->polygons = draw_involute_gear (g, (wire_p && debug_p ? 2 : wire_p)); glEndList (); } glPushMatrix(); glTranslatef (g->x, g->y, g->z); if (g->motion_blur_p && !pp->button_down_p) { /* If we're in motion-blur mode, then draw the gear so that each frame rotates it by exactly one half tooth-width, so that it looks flickery around the edges. But, revert to the normal way when the mouse button is down lest the user see overlapping polygons. */ th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1); g->motion_blur_p++; } else th = g->th; glRotatef (th, 0, 0, 1); glPushName (g->id); if (! visible_p) mi->polygon_count += draw_involute_schematic (g, wire_p); else { glCallList (g->dlist); mi->polygon_count += g->polygons; } glPopName(); glPopMatrix(); } /* Render all gears. */ static void draw_gears (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; Bool wire_p = MI_IS_WIREFRAME(mi); int i; glColor4f (1, 1, 0.8, 1); glInitNames(); for (i = 0; i < pp->ngears; i++) draw_gear (mi, i); /* draw a line connecting gears that are, uh, geared. */ if (debug_p) { static const GLfloat color[4] = {1.0, 0.0, 0.0, 1.0}; GLfloat off = 0.1; GLfloat ox=0, oy=0, oz=0; if (!wire_p) glDisable(GL_LIGHTING); glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); glColor3f (color[0], color[1], color[2]); for (i = 0; i < pp->ngears; i++) { gear *g = pp->gears[i]; glBegin(GL_LINE_STRIP); glVertex3f (g->x, g->y, g->z - off); glVertex3f (g->x, g->y, g->z + off); if (i > 0 && !g->base_p) glVertex3f (ox, oy, oz + off); glEnd(); ox = g->x; oy = g->y; oz = g->z; } if (!wire_p) glEnable(GL_LIGHTING); } } /* Mouse hit detection */ static void find_mouse_gear (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; # ifndef HAVE_JWZGLES int screen_width = MI_WIDTH (mi); int screen_height = MI_HEIGHT (mi); GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width; int x, y; int hits; pp->mouse_gear_id = 0; /* Poll mouse position */ { Window r, c; int rx, ry; unsigned int m; XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi), &r, &c, &rx, &ry, &x, &y, &m); } if (x < 0 || y < 0 || x > screen_width || y > screen_height) return; /* out of window */ /* Run OpenGL hit detector */ { GLint vp[4]; GLuint selbuf[512]; glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */ glRenderMode (GL_SELECT); glMatrixMode (GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */ gluPickMatrix (x, vp[3]-y, 5, 5, vp); gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */ glMatrixMode (GL_MODELVIEW); draw_gears (mi); /* render into "select" buffer */ glMatrixMode (GL_PROJECTION); /* restore old vp */ glPopMatrix (); glMatrixMode (GL_MODELVIEW); glFlush(); hits = glRenderMode (GL_RENDER); /* done selecting */ if (hits > 0) { int i; GLuint name_count = 0; GLuint *p = (GLuint *) selbuf; GLuint *pnames = 0; GLuint min_z = ~0; for (i = 0; i < hits; i++) { int names = *p++; if (*p < min_z) /* find match closest to screen */ { name_count = names; min_z = *p; pnames = p+2; } p += names+2; } if (name_count > 0) /* take first hit */ pp->mouse_gear_id = pnames[0]; } } #else /* HAVE_JWZGLES */ /* #### not yet implemented */ pp->mouse_gear_id = (pp->ngears > 1 ? pp->gears[1]->id : 0); return; #endif /* HAVE_JWZGLES */ } /* Window management, etc */ ENTRYPOINT void reshape_pinion (ModeInfo *mi, int width, int height) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; if (width > height * 5) { /* tiny window: show middle */ height = width * 9/16; y = -height/2; h = height / (GLfloat) width; } glViewport (0, y, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (30.0, 1/h, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); { GLfloat render_width, layout_width; pp->vp_height = 1.0; pp->vp_width = 1/h; pp->vp_left = -pp->vp_width/2; pp->vp_right = pp->vp_width/2; pp->vp_top = pp->vp_height/2; pp->vp_bottom = -pp->vp_height/2; render_width = pp->vp_width * 2; layout_width = pp->vp_width * 0.8 * gear_size; pp->render_left = -render_width/2; pp->render_right = render_width/2; pp->layout_left = pp->render_right; pp->layout_right = pp->layout_left + layout_width; } } ENTRYPOINT Bool pinion_handle_event (ModeInfo *mi, XEvent *event) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; if (gltrackball_event_handler (event, pp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &pp->button_down_p)) return True; else if (event->xany.type == KeyPress) { KeySym keysym; char c = 0; XLookupString (&event->xkey, &c, 1, &keysym, 0); if (c == ' ' && debug_one_gear_p && pp->ngears) { delete_gear (mi, pp->gears[0]); return True; } } return False; } ENTRYPOINT void init_pinion (ModeInfo *mi) { pinion_configuration *pp; MI_INIT (mi, pps); pp = &pps[MI_SCREEN(mi)]; pp->glx_context = init_GL(mi); load_fonts (mi); reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */ pp->ngears = 0; pp->gears_size = 0; pp->gears = 0; pp->plane_displacement = gear_size * 0.1; pp->trackball = gltrackball_init (False); ffwd (mi); } ENTRYPOINT void draw_pinion (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); Bool wire_p = MI_IS_WIREFRAME(mi); if (!pp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *pp->glx_context); glPushMatrix(); glRotatef(current_device_rotation(), 0, 0, 1); if (!wire_p) { GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0}; GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0}; GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0}; GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0}; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glLightfv(GL_LIGHT0, GL_POSITION, pos); glLightfv(GL_LIGHT0, GL_AMBIENT, amb); glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); glLightfv(GL_LIGHT0, GL_SPECULAR, spc); } if (!pp->button_down_p) { if (!debug_one_gear_p || pp->ngears == 0) scroll_gears (mi); spin_gears (mi); } glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glEnable(GL_CULL_FACE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix (); { gltrackball_rotate (pp->trackball); mi->polygon_count = 0; glScalef (16, 16, 16); /* map vp_width/height to the screen */ if (debug_one_gear_p) /* zoom in */ glScalef (3, 3, 3); else if (debug_p) /* show the "visible" and "layout" areas */ { GLfloat ow = pp->layout_right - pp->render_left; GLfloat rw = pp->render_right - pp->render_left; GLfloat s = (pp->vp_width / ow) * 0.85; glScalef (s, s, s); glTranslatef (-(ow - rw) / 2, 0, 0); } else { GLfloat s = 1.2; glScalef (s, s, s); /* zoom in a little more */ glRotatef (-35, 1, 0, 0); /* tilt back */ glRotatef ( 8, 0, 1, 0); /* tilt left */ glTranslatef (0.02, 0.1, 0); /* pan up */ } draw_gears (mi); if (debug_p) { if (!wire_p) glDisable(GL_LIGHTING); glColor3f (0.6, 0, 0); glBegin(GL_LINE_LOOP); glVertex3f (pp->render_left, pp->vp_top, 0); glVertex3f (pp->render_right, pp->vp_top, 0); glVertex3f (pp->render_right, pp->vp_bottom, 0); glVertex3f (pp->render_left, pp->vp_bottom, 0); glEnd(); glColor3f (0.4, 0, 0); glBegin(GL_LINES); glVertex3f (pp->vp_left, pp->vp_top, 0); glVertex3f (pp->vp_left, pp->vp_bottom, 0); glVertex3f (pp->vp_right, pp->vp_top, 0); glVertex3f (pp->vp_right, pp->vp_bottom, 0); glEnd(); glColor3f (0, 0.4, 0); glBegin(GL_LINE_LOOP); glVertex3f (pp->layout_left, pp->vp_top, 0); glVertex3f (pp->layout_right, pp->vp_top, 0); glVertex3f (pp->layout_right, pp->vp_bottom, 0); glVertex3f (pp->layout_left, pp->vp_bottom, 0); glEnd(); if (!wire_p) glEnable(GL_LIGHTING); } if (pp->draw_tick++ > 10) /* only do this every N frames */ { pp->draw_tick = 0; find_mouse_gear (mi); } } glPopMatrix (); draw_label (mi); glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_pinion (ModeInfo *mi) { pinion_configuration *pp = &pps[MI_SCREEN(mi)]; int i; if (!pp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *pp->glx_context); for (i = 0; i < pp->ngears; i++) if (pp->gears[i]) free_gear (pp->gears[i]); if (pp->gears) free (pp->gears); if (pp->trackball) gltrackball_free (pp->trackball); if (pp->font1) free_texture_font (pp->font1); if (pp->font2) free_texture_font (pp->font2); if (pp->font3) free_texture_font (pp->font3); } XSCREENSAVER_MODULE ("Pinion", pinion) #endif /* USE_GL */