1
/* pinion, Copyright (c) 2004 Jamie Zawinski <jwz@jwz.org>
3
* Permission to use, copy, modify, distribute, and sell this software and its
4
* documentation for any purpose is hereby granted without fee, provided that
5
* the above copyright notice appear in all copies and that both that
6
* copyright notice and this permission notice appear in supporting
7
* documentation. No representations are made about the suitability of this
8
* software for any purpose. It is provided "as is" without express or
12
#include <X11/Intrinsic.h>
14
extern XtAppContext app;
16
#define PROGCLASS "Pinion"
17
#define HACK_INIT init_pinion
18
#define HACK_DRAW draw_pinion
19
#define HACK_RESHAPE reshape_pinion
20
#define HACK_HANDLE_EVENT pinion_handle_event
21
#define EVENT_MASK PointerMotionMask
22
#define sws_opts xlockmore_opts
24
#define DEF_SPIN_SPEED "1.0"
25
#define DEF_SCROLL_SPEED "1.0"
26
#define DEF_GEAR_SIZE "1.0"
27
#define DEF_MAX_RPM "900"
29
#define DEFAULTS "*delay: 15000 \n" \
30
"*showFPS: False \n" \
31
"*wireframe: False \n" \
32
"*titleFont: -*-times-bold-r-normal-*-180-*\n" \
33
"*titleFont2: -*-times-bold-r-normal-*-120-*\n" \
34
"*titleFont3: -*-times-bold-r-normal-*-80-*\n" \
37
#define countof(x) (sizeof((x))/sizeof((*x)))
40
#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
42
#include "xlockmore.h"
44
#include "gltrackball.h"
48
#ifdef USE_GL /* whole file */
53
unsigned long id; /* unique name */
54
double x, y, z; /* position */
55
double r; /* radius of the gear, at middle of teeth */
56
double th; /* rotation (degrees) */
58
GLint nteeth; /* how many teeth */
59
double tooth_w, tooth_h; /* size of teeth */
61
double inner_r; /* radius of the (larger) inside hole */
62
double inner_r2; /* radius of the (smaller) inside hole, if any */
63
double inner_r3; /* yet another */
65
double thickness; /* height of the edge */
66
double thickness2; /* height of the (smaller) inside disc if any */
67
double thickness3; /* yet another */
68
int spokes; /* how many spokes inside, if any */
69
int nubs; /* how many little nubbly bits, if any */
70
double spoke_thickness; /* spoke versus hole */
71
GLfloat wobble; /* factory defect! */
72
int motion_blur_p; /* whether it's spinning too fast to draw */
73
int polygons; /* how many polys in this gear */
75
double ratio; /* gearing ratio with previous gears */
76
double rpm; /* approximate revolutions per minute */
78
Bool base_p; /* whether this gear begins a new train */
79
int coax_p; /* whether this is one of a pair of bound gears.
80
1 for first, 2 for second. */
81
double coax_thickness; /* thickness of the other gear in the pair */
82
enum { SMALL, MEDIUM, LARGE } size; /* Controls complexity of mesh. */
91
GLXContext *glx_context;
92
GLfloat vp_left, vp_right, vp_top, vp_bottom; /* default visible area */
93
GLfloat vp_width, vp_height;
94
GLfloat render_left, render_right; /* area in which gears are displayed */
95
GLfloat layout_left, layout_right; /* layout region, on the right side */
101
trackball_state *trackball;
103
unsigned long mouse_gear_id;
105
XFontStruct *xfont1, *xfont2, *xfont3;
106
GLuint font1_dlist, font2_dlist, font3_dlist;
109
} pinion_configuration;
112
static pinion_configuration *pps = NULL;
113
static GLfloat spin_speed, scroll_speed, max_rpm, gear_size;
114
static GLfloat plane_displacement = 0.1; /* distance between coaxial gears */
116
static Bool verbose_p = False; /* print progress on stderr */
117
static Bool debug_placement_p = False; /* extreme verbosity on stderr */
118
static Bool debug_p = False; /* render as flat schematic */
119
static Bool debug_one_gear_p = False; /* draw one big stationary gear */
120
static Bool wire_all_p = False; /* in wireframe, do not abbreviate */
122
static int debug_size_failures; /* for debugging messages */
123
static int debug_position_failures;
124
static unsigned long current_length; /* gear count in current train */
125
static unsigned long current_blur_length; /* how long have we been blurring? */
128
static XrmOptionDescRec opts[] = {
129
{ "-spin", ".spinSpeed", XrmoptionSepArg, 0 },
130
{ "-scroll", ".scrollSpeed", XrmoptionSepArg, 0 },
131
{ "-size", ".gearSize", XrmoptionSepArg, 0 },
132
{ "-max-rpm",".maxRPM", XrmoptionSepArg, 0 },
133
{ "-debug", ".debug", XrmoptionNoArg, "True" },
134
{ "-verbose",".verbose", XrmoptionNoArg, "True" },
137
static argtype vars[] = {
138
{&spin_speed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
139
{&scroll_speed, "scrollSpeed", "ScrollSpeed", DEF_SCROLL_SPEED, t_Float},
140
{&gear_size, "gearSize", "GearSize", DEF_GEAR_SIZE, t_Float},
141
{&max_rpm, "maxRPM", "MaxRPM", DEF_MAX_RPM, t_Float},
142
{&debug_p, "debug", "Debug", "False", t_Bool},
143
{&verbose_p, "verbose", "Verbose", "False", t_Bool},
146
ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
153
load_fonts (ModeInfo *mi)
155
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
156
load_font (mi->dpy, "titleFont", &pp->xfont1, &pp->font1_dlist);
157
load_font (mi->dpy, "titleFont2", &pp->xfont2, &pp->font2_dlist);
158
load_font (mi->dpy, "titleFont3", &pp->xfont3, &pp->font3_dlist);
163
static void rpm_string (double rpm, char *s);
166
new_label (ModeInfo *mi)
168
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
173
if (pp->mouse_gear_id)
174
for (i = 0; i < pp->ngears; i++)
175
if (pp->gears[i]->id == pp->mouse_gear_id)
185
sprintf (label, "%d teeth\n", g->nteeth);
186
rpm_string (g->rpm, label + strlen(label));
188
sprintf (label + strlen (label), "\nPolys: %d\nModel: %s (%.2f)\n",
190
(g->size == SMALL ? "small" : g->size == MEDIUM ? "medium"
192
g->tooth_h * MI_HEIGHT(mi));
195
glNewList (pp->title_list, GL_COMPILE);
200
if (MI_WIDTH(mi) >= 500 && MI_HEIGHT(mi) >= 375)
201
f = pp->xfont1, fl = pp->font1_dlist; /* big font */
202
else if (MI_WIDTH(mi) >= 350 && MI_HEIGHT(mi) >= 260)
203
f = pp->xfont2, fl = pp->font2_dlist; /* small font */
205
f = pp->xfont3, fl = pp->font3_dlist; /* tiny font */
207
glColor3f (0.8, 0.8, 0);
208
print_gl_string (mi->dpy, f, fl,
209
mi->xgwa.width, mi->xgwa.height,
210
10, mi->xgwa.height - 10,
221
/* Find the gear in the scene that is farthest to the right or left.
224
farthest_gear (ModeInfo *mi, Bool left_p)
226
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
229
double x = (left_p ? 999999 : -999999);
230
for (i = 0; i < pp->ngears; i++)
232
gear *g = pp->gears[i];
233
double gx = g->x + ((g->r + g->tooth_h) * (left_p ? -1 : 1));
234
if (left_p ? (x > gx) : (x < gx))
244
/* Compute the revolutions per minute of a gear.
247
compute_rpm (ModeInfo *mi, gear *g)
249
double fps, rpf, rps;
250
fps = (MI_PAUSE(mi) == 0 ? 999999 : 1000000.0 / MI_PAUSE(mi));
252
if (fps > 150) fps = 150; /* let's be reasonable... */
253
if (fps < 10) fps = 10;
255
rpf = (g->ratio * spin_speed) / 360.0; /* rotations per frame */
256
rps = rpf * fps; /* rotations per second */
260
/* Prints the RPM into a string, doing fancy float formatting.
263
rpm_string (double rpm, char *s)
267
if (rpm >= 0.1) sprintf (buf, "%.2f", rpm);
268
else if (rpm >= 0.001) sprintf (buf, "%.4f", rpm);
269
else if (rpm >= 0.00001) sprintf (buf, "%.8f", rpm);
270
else sprintf (buf, "%.16f",rpm);
273
while (buf[L-1] == '0') buf[--L] = 0;
274
if (buf[L-1] == '.') buf[--L] = 0;
281
/* Which of the gear's inside rings is the biggest?
284
biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
286
double r0 = (g->r - g->tooth_h/2);
287
double r1 = g->inner_r;
288
double r2 = g->inner_r2;
289
double r3 = g->inner_r3;
290
double w1 = (r1 ? r0 - r1 : r0);
291
double w2 = (r2 ? r1 - r2 : 0);
292
double w3 = (r3 ? r2 - r3 : 0);
293
double h1 = g->thickness;
294
double h2 = g->thickness2;
295
double h3 = g->thickness3;
297
if (g->spokes) w2 = 0;
299
if (w1 > w2 && w1 > w3)
301
if (posP) *posP = (r0+r1)/2;
302
if (sizeP) *sizeP = w1;
303
if (heightP) *heightP = h1;
306
else if (w2 > w1 && w2 > w3)
308
if (posP) *posP = (r1+r2)/2;
309
if (sizeP) *sizeP = w2;
310
if (heightP) *heightP = h2;
315
if (posP) *posP = (r2+r3)/2;
316
if (sizeP) *sizeP = w3;
317
if (heightP) *heightP = h3;
327
/* Create and return a new gear sized for placement next to or on top of
328
the given parent gear (if any.) Returns 0 if out of memory.
331
new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
333
gear *g = (gear *) calloc (1, sizeof (*g));
335
static unsigned long id = 0;
338
if (coaxial_p && !parent) abort();
344
if (loop_count > 1000)
345
/* The only time we loop in here is when making a coaxial gear, and
346
trying to pick a radius that is either significantly larger or
347
smaller than its parent. That shouldn't be hard, so something
348
must be really wrong if we can't do that in only a few tries.
352
/* Pick the size of the teeth.
354
if (parent && !coaxial_p) /* adjascent gears need matching teeth */
356
g->tooth_w = parent->tooth_w;
357
g->tooth_h = parent->tooth_h;
358
g->thickness = parent->thickness;
359
g->thickness2 = parent->thickness2;
360
g->thickness3 = parent->thickness3;
362
else /* gears that begin trains get any size they want */
364
double scale = (1.0 + BELLRAND(4.0)) * gear_size;
365
g->tooth_w = 0.007 * scale;
366
g->tooth_h = 0.005 * scale;
367
g->thickness = g->tooth_h * (0.1 + BELLRAND(1.5));
368
g->thickness2 = g->thickness / 4;
369
g->thickness3 = g->thickness;
372
/* Pick the number of teeth, and thus, the radius.
378
g->nteeth = 3 + (random() % 97); /* from 3 to 100 teeth */
380
if (g->nteeth < 7 && (random() % 5) != 0)
381
goto AGAIN; /* Let's make very small tooth-counts more rare */
383
c = g->nteeth * g->tooth_w * 2; /* circumference = teeth + gaps */
384
g->r = c / (M_PI * 2); /* c = 2 pi r */
390
if (! coaxial_p) break; /* yes */
391
if (g->nteeth == parent->nteeth) continue; /* ugly */
392
if (g->r < parent->r * 0.6) break; /* g much smaller than parent */
393
if (parent->r < g->r * 0.6) break; /* g much larger than parent */
398
g->color[0] = 0.5 + frand(0.5);
399
g->color[1] = 0.5 + frand(0.5);
400
g->color[2] = 0.5 + frand(0.5);
403
g->color2[0] = g->color[0] * 0.85;
404
g->color2[1] = g->color[1] * 0.85;
405
g->color2[2] = g->color[2] * 0.85;
406
g->color2[3] = g->color[3];
409
/* Decide on shape of gear interior:
410
- just a ring with teeth;
411
- that, plus a thinner in-set "plate" in the middle;
412
- that, plus a thin raised "lip" on the inner plate;
413
- or, a wide lip (really, a thicker third inner plate.)
415
if ((random() % 10) == 0)
417
/* inner_r can go all the way in; there's no inset disc. */
418
g->inner_r = (g->r * 0.1) + frand((g->r - g->tooth_h/2) * 0.8);
424
/* inner_r doesn't go in very far; inner_r2 is an inset disc. */
425
g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4);
426
g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5);
429
if (g->inner_r2 > (g->r * 0.2))
431
int nn = (random() % 10);
433
g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2);
434
else if (nn <= 7 && g->inner_r2 >= 0.1)
435
g->inner_r3 = g->inner_r2 - 0.01;
439
/* Coaxial gears need to have the same innermost hole size (for the axle.)
440
Use whichever of the two is smaller. (Modifies parent.)
444
double hole1 = (g->inner_r3 ? g->inner_r3 :
445
g->inner_r2 ? g->inner_r2 :
447
double hole2 = (parent->inner_r3 ? parent->inner_r3 :
448
parent->inner_r2 ? parent->inner_r2 :
450
double hole = (hole1 < hole2 ? hole1 : hole2);
451
if (hole <= 0) abort();
453
if (g->inner_r3) g->inner_r3 = hole;
454
else if (g->inner_r2) g->inner_r2 = hole;
455
else g->inner_r = hole;
457
if (parent->inner_r3) parent->inner_r3 = hole;
458
else if (parent->inner_r2) parent->inner_r2 = hole;
459
else parent->inner_r = hole;
462
/* If we have three discs, sometimes make the middle disc be spokes.
464
if (g->inner_r3 && ((random() % 5) == 0))
466
g->spokes = 2 + BELLRAND (5);
467
g->spoke_thickness = 1 + frand(7.0);
468
if (g->spokes == 2 && g->spoke_thickness < 2)
469
g->spoke_thickness += 1;
472
/* Sometimes add little nubbly bits, if there is room.
477
biggest_ring (g, 0, &size, 0);
478
if (size > g->r * 0.2 && (random() % 5) == 0)
480
g->nubs = 1 + (random() % 16);
481
if (g->nubs > 8) g->nubs = 1;
485
if (g->inner_r3 > g->inner_r2) abort();
486
if (g->inner_r2 > g->inner_r) abort();
487
if (g->inner_r > g->r) abort();
489
/* Decide how complex the polygon model should be.
492
double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */
493
if (pix <= 2.5) g->size = SMALL;
494
else if (pix <= 3.5) g->size = MEDIUM;
495
else g->size = LARGE;
504
/* Given a newly-created gear, place it next to its parent in the scene,
505
with its teeth meshed and the proper velocity. Returns False if it
506
didn't work. (Call this a bunch of times until either it works, or
507
you decide it's probably not going to.)
510
place_gear (ModeInfo *mi, gear *g, gear *parent, Bool coaxial_p)
512
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
514
/* If this gear takes up more than 1/3rd of the screen, it's no good.
516
if (((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_width ||
517
((g->r + g->tooth_h) * (6 / gear_size)) >= pp->vp_height)
519
if (verbose_p && debug_placement_p && 0)
521
"%s: placement: too big: %.2f @ %.2f vs %.2f x %.2f\n",
523
(g->r + g->tooth_h), gear_size,
524
pp->vp_width, pp->vp_height);
525
debug_size_failures++;
529
/* Compute this gear's velocity.
533
g->ratio = 0.8 + BELLRAND(0.4); /* 0.8-1.2 = 8-12rpm @ 60fps */
534
g->th = frand (90) * ((random() & 1) ? 1.0 : -1.0);
538
g->ratio = parent->ratio; /* bound gears have the same ratio */
540
g->rpm = parent->rpm;
541
g->wobble = parent->wobble;
545
/* Gearing ratio is the ratio of the number of teeth to previous gear
546
(which is also the ratio of the circumferences.)
548
g->ratio = (double) parent->nteeth / (double) g->nteeth;
550
/* Set our initial rotation to match that of the previous gear,
551
multiplied by the gearing ratio. (This is finessed later,
552
once we know the exact position of the gear relative to its
555
g->th = -(parent->th * g->ratio);
557
if (g->nteeth & 1) /* rotate 1/2 tooth-size if odd number of teeth */
559
double off = (180.0 / g->nteeth);
566
/* ratios are cumulative for all gears in the train. */
567
g->ratio *= parent->ratio;
571
/* Place the gear relative to the parent.
576
gear *rg = farthest_gear (mi, False);
577
double right = (rg ? rg->x + rg->r + rg->tooth_h : 0);
578
if (right < pp->layout_left) /* place off screen */
579
right = pp->layout_left;
581
g->x = right + g->r + g->tooth_h + (0.01 / gear_size);
585
if (debug_one_gear_p)
590
double off = plane_displacement;
594
g->z = parent->z + (g->r > parent->r /* small gear on top */
597
if (parent->r > g->r) /* mark which is top and which is bottom */
601
parent->wobble = 0; /* looks bad when axle moves */
610
g->coax_thickness = parent->thickness;
611
parent->coax_thickness = g->thickness;
613
/* Don't let the train get too close to or far from the screen.
614
If we're getting too close, give up on this gear.
615
(But getting very far away is fine.)
617
if (g->z >= off * 4 ||
620
if (verbose_p && debug_placement_p)
621
fprintf (stderr, "%s: placement: bad depth: %.2f\n",
623
debug_position_failures++;
627
else /* position it somewhere next to the parent. */
629
double r_off = parent->r + g->r;
632
if ((random() % 3) != 0)
633
angle = (random() % 240) - 120; /* mostly -120 to +120 degrees */
635
angle = (random() % 360) - 180; /* sometimes -180 to +180 degrees */
637
g->x = parent->x + (cos ((double) angle * (M_PI / 180)) * r_off);
638
g->y = parent->y + (sin ((double) angle * (M_PI / 180)) * r_off);
641
/* If the angle we picked would have positioned this gear
642
more than halfway off screen, that's no good. */
643
if (g->y > pp->vp_top ||
644
g->y < pp->vp_bottom)
646
if (verbose_p && debug_placement_p)
647
fprintf (stderr, "%s: placement: out of bounds: %s\n",
648
progname, (g->y > pp->vp_top ? "top" : "bottom"));
649
debug_position_failures++;
653
/* avoid accidentally changing sign of "th" in the math below. */
654
g->th += (g->th > 0 ? 360 : -360);
656
/* Adjust the rotation of the gear so that its teeth line up with its
657
parent, based on the position of the gear and the current rotation
661
double p_c = 2 * M_PI * parent->r; /* circumference of parent */
662
double g_c = 2 * M_PI * g->r; /* circumference of g */
664
double p_t = p_c * (angle/360.0); /* distance travelled along
665
circumference of parent when
666
moving "angle" degrees along
668
double g_rat = p_t / g_c; /* if travelling that distance
669
along circumference of g,
670
ratio of g's circumference
672
double g_th = 360.0 * g_rat; /* that ratio in degrees */
674
g->th += angle + g_th;
678
if (debug_one_gear_p)
684
/* If the position we picked for this gear would cause it to already
685
be visible on the screen, give up. This can happen when the train
686
is growing backwards, and we don't want to see gears flash into
689
if (g->x - g->r - g->tooth_h < pp->render_right)
691
if (verbose_p && debug_placement_p)
692
fprintf (stderr, "%s: placement: out of bounds: left\n", progname);
693
debug_position_failures++;
697
/* If the position we picked for this gear causes it to overlap
698
with any earlier gear in the train, give up.
703
for (i = pp->ngears-1; i >= 0; i--)
705
gear *og = pp->gears[i];
707
if (og == g) continue;
708
if (og == parent) continue;
709
if (g->z != og->z) continue; /* Ignore unless on same layer */
711
/* Collision detection without sqrt:
712
d = sqrt(a^2 + b^2) d^2 = a^2 + b^2
713
d < r1 + r2 d^2 < (r1 + r2)^2
715
if (((g->x - og->x) * (g->x - og->x) +
716
(g->y - og->y) * (g->y - og->y)) <
717
((g->r + g->tooth_h + og->r + og->tooth_h) *
718
(g->r + g->tooth_h + og->r + og->tooth_h)))
720
if (verbose_p && debug_placement_p)
721
fprintf (stderr, "%s: placement: collision with %lu\n",
723
debug_position_failures++;
732
/* Make deeper gears be darker.
735
double depth = g->z / plane_displacement;
736
double brightness = 1 + (depth / 6);
738
if (brightness < limit) brightness = limit;
739
if (brightness > 1/limit) brightness = 1/limit;
740
g->color[0] *= brightness;
741
g->color[1] *= brightness;
742
g->color[2] *= brightness;
743
g->color2[0] *= brightness;
744
g->color2[1] *= brightness;
745
g->color2[2] *= brightness;
748
/* If a single frame of animation would cause the gear to rotate by
749
more than 1/2 the size of a single tooth, then it won't look right:
750
the gear will appear to be turning at some lower harmonic of its
754
double ratio = g->ratio * spin_speed;
755
double blur_limit = 180.0 / g->nteeth;
757
if (ratio > blur_limit)
758
g->motion_blur_p = 1;
762
/* ride until the wheels fall off... */
763
if (ratio > blur_limit * 0.7) g->wobble += (random() % 2);
764
if (ratio > blur_limit * 0.9) g->wobble += (random() % 2);
765
if (ratio > blur_limit * 1.1) g->wobble += (random() % 2);
766
if (ratio > blur_limit * 1.3) g->wobble += (random() % 2);
767
if (ratio > blur_limit * 1.5) g->wobble += (random() % 2);
768
if (ratio > blur_limit * 1.7) g->wobble += (random() % 2);
779
glDeleteLists (g->dlist, 1);
784
/* Make a new gear, place it next to its parent in the scene,
785
with its teeth meshed and the proper velocity. Returns the gear;
786
or 0 if it didn't work. (Call this a bunch of times until either
787
it works, or you decide it's probably not going to.)
790
place_new_gear (ModeInfo *mi, gear *parent, Bool coaxial_p)
792
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
799
if (loop_count >= 100)
807
g = new_gear (mi, parent, coaxial_p);
808
if (!g) return 0; /* out of memory? */
810
if (place_gear (mi, g, parent, coaxial_p))
816
/* We got a gear, and it is properly positioned.
817
Insert it in the scene.
819
if (pp->ngears + 2 >= pp->gears_size)
821
pp->gears_size += 100;
822
pp->gears = (gear **) realloc (pp->gears,
823
pp->gears_size * sizeof (*pp->gears));
826
fprintf (stderr, "%s: out of memory (%d gears)\n",
827
progname, pp->gears_size);
831
pp->gears[pp->ngears++] = g;
836
static void delete_gear (ModeInfo *mi, gear *g);
839
push_gear (ModeInfo *mi)
841
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
843
gear *parent = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
845
Bool tried_coaxial_p = False;
846
Bool coaxial_p = False;
847
Bool last_ditch_coax_p = False;
850
debug_size_failures = 0;
851
debug_position_failures = 0;
855
if (loop_count > 100) abort(); /* we're doomed! */
859
/* If the gears are turning at LUDICROUS SPEED, unhook the train to
860
reset things to a sane velocity.
862
10,000 RPM at 30 FPS = 5.5 rotations per frame.
863
1,000 RPM at 30 FPS = 0.5 rotations per frame.
864
600 RPM at 30 FPS = 3 frames per rotation.
865
200 RPM at 30 FPS = 9 frames per rotation.
866
100 RPM at 30 FPS = 18 frames per rotation.
867
50 RPM at 30 FPS = 36 frames per rotation.
868
10 RPM at 30 FPS = 3 sec per rotation.
869
1 RPM at 30 FPS = 30 sec per rotation.
870
.5 RPM at 30 FPS = 1 min per rotation.
871
.1 RPM at 30 FPS = 5 min per rotation.
873
if (parent && parent->rpm > max_rpm)
878
rpm_string (parent->rpm, buf);
879
fprintf (stderr, "%s: ludicrous speed! %s\n\n", progname, buf);
884
/* If the last N gears we've placed have all been motion-blurred, then
885
it's a safe guess that we've wandered off into the woods and aren't
886
coming back. Bail on this train.
888
if (current_blur_length >= 10)
891
fprintf (stderr, "%s: it's a blurpocalypse!\n\n", progname);
897
/* Sometimes, try to make a coaxial gear.
899
if (parent && !parent->coax_p && (random() % 40) == 0)
901
tried_coaxial_p = True;
903
g = place_new_gear (mi, parent, coaxial_p);
906
/* Try to make a regular gear.
911
g = place_new_gear (mi, parent, coaxial_p);
914
/* If we couldn't make a regular gear, then try to make a coxial gear
915
(unless we already tried that.)
917
if (!g && !tried_coaxial_p && parent && !parent->coax_p)
919
tried_coaxial_p = True;
921
g = place_new_gear (mi, parent, coaxial_p);
923
last_ditch_coax_p = True;
926
/* If we couldn't do that either, then the train has hit a dead end:
933
fprintf (stderr, "%s: dead end!\n\n", progname);
935
g = place_new_gear (mi, parent, coaxial_p);
940
/* Unable to make/place any gears at all!
941
This can happen if we've backed ourself into a corner very near
942
the top-right or bottom-right corner of the growth zone.
943
It's time to add a gear, but there's no room to add one!
944
In that case, let's just wipe all the gears that are in the
945
growth zone and try again.
949
if (verbose_p && debug_placement_p)
951
"%s: placement: resetting growth zone! "
952
"failed: %d size, %d pos\n",
954
debug_size_failures, debug_position_failures);
955
for (i = pp->ngears-1; i >= 0; i--)
957
gear *g = pp->gears[i];
958
if (g->x - g->r - g->tooth_h < pp->render_left)
966
if (g->x != parent->x) abort();
967
if (g->y != parent->y) abort();
968
if (g->z == parent->z) abort();
969
if (g->r == parent->r) abort();
970
if (g->th != parent->th) abort();
971
if (g->ratio != parent->ratio) abort();
972
if (g->rpm != parent->rpm) abort();
977
fprintf (stderr, "%s: %5lu ", progname, g->id);
979
fputc ((g->motion_blur_p ? '*' : ' '), stderr);
980
fputc (((g->coax_p && last_ditch_coax_p) ? '2' :
981
g->coax_p ? '1' : ' '),
984
fprintf (stderr, " %2d%%",
985
(int) (g->r * 2 * 100 / pp->vp_height));
986
fprintf (stderr, " %2d teeth", g->nteeth);
987
fprintf (stderr, " %3.0f rpm;", g->rpm);
990
char buf1[50], buf2[50], buf3[100];
991
*buf1 = 0; *buf2 = 0; *buf3 = 0;
992
if (debug_size_failures)
993
sprintf (buf1, "%3d sz", debug_size_failures);
994
if (debug_position_failures)
995
sprintf (buf2, "%2d pos", debug_position_failures);
997
sprintf (buf3, " tries: %-7s%s", buf1, buf2);
998
fprintf (stderr, "%-21s", buf3);
1001
if (g->base_p) fprintf (stderr, " RESET %lu", current_length);
1002
fprintf (stderr, "\n");
1010
if (g->motion_blur_p)
1011
current_blur_length++;
1013
current_blur_length = 0;
1018
/* Remove the given gear from the scene and free it.
1021
delete_gear (ModeInfo *mi, gear *g)
1023
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1026
for (i = 0; i < pp->ngears; i++) /* find this gear in the list */
1027
if (pp->gears[i] == g) break;
1028
if (pp->gears[i] != g) abort();
1030
for (; i < pp->ngears-1; i++) /* pull later entries forward */
1031
pp->gears[i] = pp->gears[i+1];
1038
/* Update the position of each gear in the scene.
1041
scroll_gears (ModeInfo *mi)
1043
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1046
for (i = 0; i < pp->ngears; i++)
1047
pp->gears[i]->x -= (scroll_speed * 0.002);
1049
/* if the right edge of any gear is off screen to the left, delete it.
1051
for (i = pp->ngears-1; i >= 0; i--)
1053
gear *g = pp->gears[i];
1054
if (g->x + g->r + g->tooth_h < pp->render_left)
1055
delete_gear (mi, g);
1058
/* if the right edge of the last-added gear is left of the right edge
1059
of the layout area, add another gear.
1064
gear *g = (pp->ngears <= 0 ? 0 : pp->gears[pp->ngears-1]);
1065
if (!g || g->x + g->r + g->tooth_h < pp->layout_right)
1070
if (debug_one_gear_p) break;
1074
if (i > 1 && verbose_p)
1075
fprintf (stderr, "%s: pushed %d gears\n", progname, i);
1080
/* Update the rotation of each gear in the scene.
1083
spin_gears (ModeInfo *mi)
1085
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1088
for (i = 0; i < pp->ngears; i++)
1090
gear *g = pp->gears[i];
1091
double off = (g->ratio * spin_speed);
1101
/* Run the animation fast (without displaying anything) until the first
1102
gear is just about to come on screen. This is to avoid a big delay
1103
with a blank screen when -scroll is low.
1108
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1109
if (debug_one_gear_p) return;
1112
gear *g = farthest_gear (mi, True);
1113
if (g && g->x - g->r - g->tooth_h/2 <= pp->vp_right * 0.88)
1121
/* Rendering the 3D objects into the scene.
1125
/* Draws an uncapped tube of the given radius extending from top to bottom,
1126
with faces pointing either in or out.
1129
draw_ring (ModeInfo *mi, int segments,
1130
GLfloat r, GLfloat top, GLfloat bottom, Bool in_p)
1134
Bool wire_p = MI_IS_WIREFRAME(mi);
1135
GLfloat width = M_PI * 2 / segments;
1139
glFrontFace (in_p ? GL_CCW : GL_CW);
1140
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1141
for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1143
GLfloat th = i * width;
1144
GLfloat cth = cos(th);
1145
GLfloat sth = sin(th);
1147
glNormal3f (-cth, -sth, 0);
1149
glNormal3f (cth, sth, 0);
1150
glVertex3f (cth * r, sth * r, top);
1151
glVertex3f (cth * r, sth * r, bottom);
1159
glBegin (GL_LINE_LOOP);
1160
for (i = 0; i < segments; i++)
1162
GLfloat th = i * width;
1163
glVertex3f (cos(th) * r, sin(th) * r, top);
1166
glBegin (GL_LINE_LOOP);
1167
for (i = 0; i < segments; i++)
1169
GLfloat th = i * width;
1170
glVertex3f (cos(th) * r, sin(th) * r, bottom);
1179
/* Draws a donut-shaped disc between the given radii,
1180
with faces pointing either up or down.
1181
The first radius may be 0, in which case, a filled disc is drawn.
1184
draw_disc (ModeInfo *mi, int segments,
1185
GLfloat ra, GLfloat rb, GLfloat z, Bool up_p)
1189
Bool wire_p = MI_IS_WIREFRAME(mi);
1190
GLfloat width = M_PI * 2 / segments;
1192
if (ra < 0) abort();
1193
if (rb <= 0) abort();
1196
glFrontFace (up_p ? GL_CW : GL_CCW);
1198
glFrontFace (up_p ? GL_CCW : GL_CW);
1201
glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
1203
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1205
glNormal3f (0, 0, (up_p ? -1 : 1));
1207
if (ra == 0 && !wire_p)
1208
glVertex3f (0, 0, z);
1210
for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
1212
GLfloat th = i * width;
1213
GLfloat cth = cos(th);
1214
GLfloat sth = sin(th);
1215
if (wire_p || ra != 0)
1216
glVertex3f (cth * ra, sth * ra, z);
1217
glVertex3f (cth * rb, sth * rb, z);
1225
/* Draws N thick radial lines between the given radii,
1226
with faces pointing either up or down.
1229
draw_spokes (ModeInfo *mi, int n, GLfloat thickness, int segments,
1230
GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2)
1234
Bool wire_p = MI_IS_WIREFRAME(mi);
1237
int insegs, outsegs;
1241
if (ra <= 0 || rb <= 0) abort();
1245
while (segments2 < segments) /* need a multiple of N >= segments */
1246
segments2 += n; /* (yes, this is a moronic way to find that) */
1248
insegs = ((float) (segments2 / n) + 0.5) / thickness;
1249
outsegs = (segments2 / n) - insegs;
1250
if (insegs <= 0) insegs = 1;
1251
if (outsegs <= 0) outsegs = 1;
1253
segments2 = (insegs + outsegs) * n;
1254
width = M_PI * 2 / segments2;
1258
for (i = 0; i < segments2; i++, tick++)
1260
GLfloat th1 = i * width;
1261
GLfloat th2 = th1 + width;
1262
GLfloat cth1 = cos(th1);
1263
GLfloat sth1 = sin(th1);
1264
GLfloat cth2 = cos(th2);
1265
GLfloat sth2 = sin(th2);
1268
int changed = (i == 0);
1270
if (state == 0 && tick == insegs)
1271
tick = 0, state = 1, changed = 1;
1272
else if (state == 1 && tick == outsegs)
1273
tick = 0, state = 0, changed = 1;
1275
if ((state == 1 || /* in */
1276
(state == 0 && changed)) &&
1277
(!wire_p || wire_all_p))
1280
glFrontFace (GL_CCW);
1281
glBegin (wire_p ? GL_LINES : GL_QUADS);
1282
glNormal3f (0, 0, -1);
1283
glVertex3f (cth1 * ra, sth1 * ra, z1);
1284
glVertex3f (cth1 * rb, sth1 * rb, z1);
1285
glVertex3f (cth2 * rb, sth2 * rb, z1);
1286
glVertex3f (cth2 * ra, sth2 * ra, z1);
1291
glFrontFace (GL_CW);
1292
glBegin (wire_p ? GL_LINES : GL_QUADS);
1293
glNormal3f (0, 0, 1);
1294
glVertex3f (cth1 * ra, sth1 * ra, z2);
1295
glVertex3f (cth1 * rb, sth1 * rb, z2);
1296
glVertex3f (cth2 * rb, sth2 * rb, z2);
1297
glVertex3f (cth2 * ra, sth2 * ra, z2);
1302
if (state == 1 && changed) /* entering "in" state */
1305
glFrontFace (GL_CW);
1306
glBegin (wire_p ? GL_LINES : GL_QUADS);
1307
do_normal (cth1 * rb, sth1 * rb, z1,
1308
cth1 * ra, sth1 * ra, z1,
1309
cth1 * rb, sth1 * rb, z2);
1310
glVertex3f (cth1 * ra, sth1 * ra, z1);
1311
glVertex3f (cth1 * rb, sth1 * rb, z1);
1312
glVertex3f (cth1 * rb, sth1 * rb, z2);
1313
glVertex3f (cth1 * ra, sth1 * ra, z2);
1318
if (state == 0 && changed) /* entering "out" state */
1321
glFrontFace (GL_CCW);
1322
glBegin (wire_p ? GL_LINES : GL_QUADS);
1323
do_normal (cth2 * ra, sth2 * ra, z1,
1324
cth2 * rb, sth2 * rb, z1,
1325
cth2 * rb, sth2 * rb, z2);
1326
glVertex3f (cth2 * ra, sth2 * ra, z1);
1327
glVertex3f (cth2 * rb, sth2 * rb, z1);
1328
glVertex3f (cth2 * rb, sth2 * rb, z2);
1329
glVertex3f (cth2 * ra, sth2 * ra, z2);
1341
/* Draws some bumps (embedded cylinders) on the gear.
1344
draw_gear_nubs (ModeInfo *mi, gear *g)
1346
Bool wire_p = MI_IS_WIREFRAME(mi);
1349
int steps = (g->size != LARGE ? 5 : 20);
1350
double r, size, height;
1355
if (! g->nubs) return 0;
1357
which = biggest_ring (g, &r, &size, &height);
1361
cc = (which == 1 ? g->color : g->color2);
1362
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
1364
width = M_PI * 2 / g->nubs;
1365
off = M_PI / (g->nteeth * 2); /* align first nub with a tooth */
1367
for (i = 0; i < g->nubs; i++)
1369
GLfloat th = (i * width) + off;
1371
glTranslatef (cos(th) * r, sin(th) * r, 0);
1373
if (wire_p && !wire_all_p)
1374
polys += draw_ring (mi, (g->size == LARGE ? steps/2 : steps),
1378
polys += draw_disc (mi, steps, 0, size, -height, True);
1379
polys += draw_disc (mi, steps, 0, size, height, False);
1380
polys += draw_ring (mi, steps, size, -height, height, False);
1389
/* Draws a much simpler representation of a gear.
1392
draw_gear_schematic (ModeInfo *mi, gear *g)
1394
Bool wire_p = MI_IS_WIREFRAME(mi);
1397
GLfloat width = M_PI * 2 / g->nteeth;
1399
if (!wire_p) glDisable(GL_LIGHTING);
1400
glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
1403
for (i = 0; i < g->nteeth; i++)
1405
GLfloat th = (i * width) + (width/4);
1406
glVertex3f (0, 0, -g->thickness/2);
1407
glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1412
glBegin (GL_LINE_LOOP);
1413
for (i = 0; i < g->nteeth; i++)
1415
GLfloat th = (i * width) + (width/4);
1416
glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
1421
if (!wire_p) glEnable(GL_LIGHTING);
1426
/* Renders all the interior (non-toothy) parts of a gear:
1427
the discs, axles, etc.
1430
draw_gear_interior (ModeInfo *mi, gear *g)
1432
Bool wire_p = MI_IS_WIREFRAME(mi);
1435
int steps = g->nteeth * 2;
1436
if (steps < 10) steps = 10;
1437
if ((wire_p && !wire_all_p) || g->size != LARGE) steps /= 2;
1438
if (g->size != LARGE && steps > 16) steps = 16;
1440
/* ring 1 (facing in) is done in draw_gear_teeth */
1442
/* ring 2 (facing in) and disc 2
1446
GLfloat ra = g->inner_r * 1.04; /* slightly larger than inner_r */
1447
GLfloat rb = g->inner_r2; /* since the points don't line up */
1448
GLfloat za = -g->thickness2/2;
1449
GLfloat zb = g->thickness2/2;
1451
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
1453
if ((g->coax_p != 1 && !g->inner_r3) ||
1454
(wire_p && wire_all_p))
1455
polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1457
if (wire_p && wire_all_p)
1458
polys += draw_ring (mi, steps, ra, za, zb, True); /* ring facing in */
1461
polys += draw_spokes (mi, g->spokes, g->spoke_thickness,
1462
steps, ra, rb, za, zb);
1463
else if (!wire_p || wire_all_p)
1465
polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1466
polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1470
/* ring 3 (facing in and out) and disc 3
1474
GLfloat ra = g->inner_r2;
1475
GLfloat rb = g->inner_r3;
1476
GLfloat za = -g->thickness3/2;
1477
GLfloat zb = g->thickness3/2;
1479
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1481
polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1483
if (g->coax_p != 1 || (wire_p && wire_all_p))
1484
polys += draw_ring (mi, steps, rb, za, zb, True); /* ring facing in */
1486
if (!wire_p || wire_all_p)
1488
polys += draw_disc (mi, steps, ra, rb, za, True); /* top plate */
1489
polys += draw_disc (mi, steps, ra, rb, zb, False); /* bottom plate */
1497
GLfloat cap_height = g->coax_thickness/3;
1499
GLfloat ra = (g->inner_r3 ? g->inner_r3 :
1500
g->inner_r2 ? g->inner_r2 :
1502
GLfloat za = -(g->thickness/2 + cap_height);
1503
GLfloat zb = g->coax_thickness/2 + plane_displacement + cap_height;
1505
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1507
if (wire_p && !wire_all_p) steps /= 2;
1509
polys += draw_ring (mi, steps, ra, za, zb, False); /* ring facing out */
1511
if (!wire_p || wire_all_p)
1513
polys += draw_disc (mi, steps, 0, ra, za, True); /* top plate */
1514
polys += draw_disc (mi, steps, 0, ra, zb, False); /* bottom plate */
1521
/* gear_teeth_geometry computes the vertices and normals of the teeth
1522
of a gear. This is the heavy lifting: there are a ton of polygons
1523
around the perimiter of a gear, and the normals are difficult (not
1524
radial or right angles.)
1526
It would be nice if we could cache this, but the numbers are
1527
different for essentially every gear:
1529
- Every gear has a different inner_r, so the vertices of the
1530
inner ring (and thus, the triangle fans on the top and bottom
1531
faces) are different in a non-scalable way.
1533
- If the ratio between tooth_w and tooth_h changes, the normals
1534
on the outside edges of the teeth are invalid (this can happen
1535
every time we start a new train.)
1537
So instead, we rely on OpenGL display lists to do the cacheing for
1538
us -- we only compute all these normals once per gear, instead of
1539
once per gear per frame.
1545
XYZ *fnormals; /* face normals */
1546
XYZ *pnormals; /* point normals */
1551
tooth_normals (tooth_face *f)
1555
/* Compute the face normals for each facet. */
1556
for (i = 0; i < f->npoints; i++)
1560
int b = (i == f->npoints-1 ? 0 : i+1);
1565
f->fnormals[i] = calc_normal (p1, p2, p3);
1568
/* From the face normals, compute the vertex normals
1569
(by averaging the normals of adjascent faces.)
1571
for (i = 0; i < f->npoints; i++)
1573
int a = (i == 0 ? f->npoints-1 : i-1);
1575
XYZ n1 = f->fnormals[a]; /* normal of [i-1 - i] face */
1576
XYZ n2 = f->fnormals[b]; /* normal of [i - i+1] face */
1577
f->pnormals[i].x = (n1.x + n2.x) / 2;
1578
f->pnormals[i].y = (n1.y + n2.y) / 2;
1579
f->pnormals[i].z = (n1.z + n2.z) / 2;
1585
gear_teeth_geometry (ModeInfo *mi, gear *g,
1586
tooth_face *orim, /* outer rim (the teeth) */
1587
tooth_face *irim) /* inner rim (the hole) */
1590
int ppt = 9; /* max points per tooth */
1591
GLfloat width = M_PI * 2 / g->nteeth;
1592
GLfloat rh = g->tooth_h;
1595
/* Approximate shape of an "involute" gear tooth.
1598
th0 th1 th2 th3 th4 th5 th6 th7 th8 th9 th10
1599
: : : : : : : : : : :
1600
: : : : : : : : : : :
1601
r0 ........:..:..:...___________...:..:..:......:......:..
1602
: : : /: : :\ : : : : :
1603
: : : / : : : \ : : : : :
1604
: : :/ : : : \: : : : :
1605
r1 ........:.....@...:....:....:...@..:..:......:......:..
1606
: : @: : : : :@ : : : :
1607
(R) ...........:...@.:...:....:....:...:.@..........:......:......
1608
: :@ : : : : : @: : : :
1609
r2 ........:..@..:...:....:....:...:..@:........:......:..
1610
: /: : : : : : :\ : : :
1611
:/ : : : : : : : \: : : /
1612
r3 ......__/..:..:...:....:....:...:..:..\______________/
1613
: : : : : : : : : : :
1614
| : : : : : : : | : :
1615
: : : : : : : : : : :
1616
| : : : : : : : | : :
1617
r4 ......__:_____________________________:________________
1624
r[0] = R + (rh * 0.5);
1625
r[1] = R + (rh * 0.25);
1626
r[2] = R - (r[1]-R);
1627
r[3] = R - (r[0]-R);
1630
th[0] = -tw * (g->size == SMALL ? 0.5 : g->size == MEDIUM ? 0.41 : 0.45);
1632
th[2] = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
1633
th[3] = -tw * (g->size == MEDIUM ? 0.1 : 0.04);
1640
th[10]= th[0] + width;
1643
orim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
1644
orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
1645
orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
1648
irim->points = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
1649
irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
1650
irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
1652
if (!orim->points || !orim->pnormals || !orim->fnormals ||
1653
!irim->points || !irim->pnormals || !irim->fnormals)
1655
fprintf (stderr, "%s: out of memory\n", progname);
1659
/* First, compute the coordinates of every point used for every tooth.
1661
for (i = 0; i < g->nteeth; i++)
1663
GLfloat TH = (i * width) + (width/4);
1666
# define PUSH(OPR,IPR,PTH) \
1667
orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
1668
orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
1670
irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
1671
irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
1674
if (g->size == SMALL)
1676
PUSH(3, 4, 0); /* tooth left 1 */
1677
PUSH(0, 4, 4); /* tooth top middle */
1679
else if (g->size == MEDIUM)
1681
PUSH(3, 4, 0); /* tooth left 1 */
1682
PUSH(0, 4, 3); /* tooth top left */
1683
PUSH(0, 4, 5); /* tooth top right */
1684
PUSH(3, 4, 8); /* tooth right 3 */
1686
else if (g->size == LARGE)
1688
PUSH(3, 4, 0); /* tooth left 1 */
1689
PUSH(2, 4, 1); /* tooth left 2 */
1690
PUSH(1, 4, 2); /* tooth left 3 */
1691
PUSH(0, 4, 3); /* tooth top left */
1692
PUSH(0, 4, 5); /* tooth top right */
1693
PUSH(1, 4, 6); /* tooth right 1 */
1694
PUSH(2, 4, 7); /* tooth right 2 */
1695
PUSH(3, 4, 8); /* tooth right 3 */
1696
PUSH(3, 4, 9); /* gap top */
1702
if (i == 0 && orim->npoints > ppt) abort(); /* go update "ppt"! */
1705
tooth_normals (orim);
1706
tooth_normals (irim);
1710
/* Renders all teeth of a gear.
1713
draw_gear_teeth (ModeInfo *mi, gear *g)
1715
Bool wire_p = MI_IS_WIREFRAME(mi);
1716
Bool show_normals_p = False;
1720
GLfloat z1 = -g->thickness/2;
1721
GLfloat z2 = g->thickness/2;
1723
tooth_face orim, irim;
1724
gear_teeth_geometry (mi, g, &orim, &irim);
1726
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1728
/* Draw the outer rim (the teeth)
1729
(In wire mode, this draws just the upright lines.)
1731
glFrontFace (GL_CW);
1732
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1733
for (i = 0; i < orim.npoints; i++)
1735
glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
1736
glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1737
glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1739
/* Show the face normal vectors */
1740
if (wire_p && show_normals_p)
1742
XYZ n = orim.fnormals[i];
1744
int b = (i == orim.npoints-1 ? 0 : i+1);
1745
GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
1746
GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
1747
GLfloat z = (z1 + z2) / 2;
1748
glVertex3f (x, y, z);
1749
glVertex3f (x + n.x, y + n.y, z);
1752
/* Show the vertex normal vectors */
1753
if (wire_p && show_normals_p)
1755
XYZ n = orim.pnormals[i];
1756
GLfloat x = orim.points[i].x;
1757
GLfloat y = orim.points[i].y;
1758
GLfloat z = (z1 + z2) / 2;
1759
glVertex3f (x, y, z);
1760
glVertex3f (x + n.x, y + n.y, z);
1764
if (!wire_p) /* close the quad loop */
1766
glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
1767
glVertex3f (orim.points[0].x, orim.points[0].y, z1);
1768
glVertex3f (orim.points[0].x, orim.points[0].y, z2);
1770
polys += orim.npoints;
1773
/* Draw the outer rim circles, in wire mode */
1776
glBegin (GL_LINE_LOOP);
1777
for (i = 0; i < orim.npoints; i++)
1778
glVertex3f (orim.points[i].x, orim.points[i].y, z1);
1780
glBegin (GL_LINE_LOOP);
1781
for (i = 0; i < orim.npoints; i++)
1782
glVertex3f (orim.points[i].x, orim.points[i].y, z2);
1787
/* Draw the inner rim (the hole)
1788
(In wire mode, this draws just the upright lines.)
1790
glFrontFace (GL_CCW);
1791
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1792
for (i = 0; i < irim.npoints; i++)
1794
glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
1795
glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1796
glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1798
/* Show the face normal vectors */
1799
if (wire_p && show_normals_p)
1801
XYZ n = irim.fnormals[i];
1803
int b = (i == irim.npoints-1 ? 0 : i+1);
1804
GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
1805
GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
1806
GLfloat z = (z1 + z2) / 2;
1807
glVertex3f (x, y, z);
1808
glVertex3f (x - n.x, y - n.y, z);
1811
/* Show the vertex normal vectors */
1812
if (wire_p && show_normals_p)
1814
XYZ n = irim.pnormals[i];
1815
GLfloat x = irim.points[i].x;
1816
GLfloat y = irim.points[i].y;
1817
GLfloat z = (z1 + z2) / 2;
1818
glVertex3f (x, y, z);
1819
glVertex3f (x - n.x, y - n.y, z);
1823
if (!wire_p) /* close the quad loop */
1825
glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
1826
glVertex3f (irim.points[0].x, irim.points[0].y, z1);
1827
glVertex3f (irim.points[0].x, irim.points[0].y, z2);
1829
polys += irim.npoints;
1832
/* Draw the inner rim circles, in wire mode
1836
glBegin (GL_LINE_LOOP);
1837
for (i = 0; i < irim.npoints; i++)
1838
glVertex3f (irim.points[i].x, irim.points[i].y, z1);
1840
glBegin (GL_LINE_LOOP);
1841
for (i = 0; i < irim.npoints; i++)
1842
glVertex3f (irim.points[i].x, irim.points[i].y, z2);
1847
/* Draw the side (the flat bit)
1849
if (!wire_p || wire_all_p)
1852
if (irim.npoints != orim.npoints) abort();
1853
for (z = z1; z <= z2; z += z2-z1)
1855
glFrontFace (z == z1 ? GL_CCW : GL_CW);
1856
glNormal3f (0, 0, z);
1857
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
1858
for (i = 0; i < orim.npoints; i++)
1860
glVertex3f (orim.points[i].x, orim.points[i].y, z);
1861
glVertex3f (irim.points[i].x, irim.points[i].y, z);
1863
if (!wire_p) /* close the quad loop */
1865
glVertex3f (orim.points[0].x, orim.points[0].y, z);
1866
glVertex3f (irim.points[0].x, irim.points[0].y, z);
1868
polys += orim.npoints;
1874
free (irim.fnormals);
1875
free (irim.pnormals);
1878
free (orim.fnormals);
1879
free (orim.pnormals);
1885
/* Render one gear, unrotated at 0,0.
1888
draw_gear_1 (ModeInfo *mi, gear *g)
1890
Bool wire_p = MI_IS_WIREFRAME(mi);
1893
static GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
1894
static GLfloat shiny = 128.0;
1896
glMaterialfv (GL_FRONT, GL_SPECULAR, spec);
1897
glMateriali (GL_FRONT, GL_SHININESS, shiny);
1898
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
1899
glColor3f (g->color[0], g->color[1], g->color[2]);
1901
if (debug_p && wire_p)
1902
polys += draw_gear_schematic (mi, g);
1906
glRotatef (g->wobble, 1, 0, 0);
1907
polys += draw_gear_teeth (mi, g);
1908
polys += draw_gear_interior (mi, g);
1909
polys += draw_gear_nubs (mi, g);
1916
/* Render one gear in the proper position, creating the gear's
1917
display list first if necessary.
1920
draw_gear (ModeInfo *mi, int which)
1922
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1923
gear *g = pp->gears[which];
1926
Bool visible_p = (g->x + g->r + g->tooth_h >= pp->render_left &&
1927
g->x - g->r - g->tooth_h <= pp->render_right);
1929
if (!visible_p && !debug_p)
1934
g->dlist = glGenLists (1);
1937
/* I don't know how many display lists a GL implementation
1938
is supposed to provide, but hopefully it's more than
1939
"a few hundred", or we'll be in trouble...
1941
check_gl_error ("glGenLists");
1945
glNewList (g->dlist, GL_COMPILE);
1946
g->polygons = draw_gear_1 (mi, g);
1952
glTranslatef (g->x, g->y, g->z);
1954
if (g->motion_blur_p && !pp->button_down_p)
1956
/* If we're in motion-blur mode, then draw the gear so that each
1957
frame rotates it by exactly one half tooth-width, so that it
1958
looks flickery around the edges. But, revert to the normal
1959
way when the mouse button is down lest the user see overlapping
1962
th = g->motion_blur_p * 180.0 / g->nteeth * (g->th > 0 ? 1 : -1);
1968
glRotatef (th, 0, 0, 1);
1973
mi->polygon_count += draw_gear_schematic (mi, g);
1976
glCallList (g->dlist);
1977
mi->polygon_count += g->polygons;
1985
/* Render all gears.
1988
draw_gears (ModeInfo *mi)
1990
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
1991
Bool wire_p = MI_IS_WIREFRAME(mi);
1994
glColor4f (1, 1, 0.8, 1);
1998
for (i = 0; i < pp->ngears; i++)
2001
/* draw a line connecting gears that are, uh, geared. */
2004
static GLfloat color[4] = {1.0, 0.0, 0.0, 1.0};
2006
GLfloat ox=0, oy=0, oz=0;
2008
if (!wire_p) glDisable(GL_LIGHTING);
2009
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
2010
glColor3f (color[0], color[1], color[2]);
2012
for (i = 0; i < pp->ngears; i++)
2014
gear *g = pp->gears[i];
2015
glBegin(GL_LINE_STRIP);
2016
glVertex3f (g->x, g->y, g->z - off);
2017
glVertex3f (g->x, g->y, g->z + off);
2018
if (i > 0 && !g->base_p)
2019
glVertex3f (ox, oy, oz + off);
2025
if (!wire_p) glEnable(GL_LIGHTING);
2030
/* Mouse hit detection
2033
find_mouse_gear (ModeInfo *mi)
2035
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2036
int screen_width = MI_WIDTH (mi);
2037
int screen_height = MI_HEIGHT (mi);
2038
GLfloat h = (GLfloat) screen_height / (GLfloat) screen_width;
2042
pp->mouse_gear_id = 0;
2044
/* Poll mouse position */
2049
XQueryPointer (MI_DISPLAY (mi), MI_WINDOW (mi),
2050
&r, &c, &rx, &ry, &x, &y, &m);
2053
if (x < 0 || y < 0 || x > screen_width || y > screen_height)
2054
return; /* out of window */
2056
/* Run OpenGL hit detector */
2061
glSelectBuffer (sizeof(selbuf), selbuf); /* set up "select" mode */
2062
glRenderMode (GL_SELECT);
2063
glMatrixMode (GL_PROJECTION);
2066
glGetIntegerv (GL_VIEWPORT, vp); /* save old vp */
2067
gluPickMatrix (x, vp[3]-y, 5, 5, vp);
2068
gluPerspective (30.0, 1/h, 1.0, 100.0); /* must match reshape_pinion() */
2069
glMatrixMode (GL_MODELVIEW);
2071
draw_gears (mi); /* render into "select" buffer */
2073
glMatrixMode (GL_PROJECTION); /* restore old vp */
2075
glMatrixMode (GL_MODELVIEW);
2077
hits = glRenderMode (GL_RENDER); /* done selecting */
2082
GLuint name_count = 0;
2083
GLuint *p = (GLuint *) selbuf;
2087
for (i = 0; i < hits; i++)
2090
if (*p < min_z) /* find match closest to screen */
2099
if (name_count > 0) /* take first hit */
2100
pp->mouse_gear_id = pnames[0];
2106
/* Window management, etc
2109
reshape_pinion (ModeInfo *mi, int width, int height)
2111
GLfloat h = (GLfloat) height / (GLfloat) width;
2112
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2114
glViewport (0, 0, (GLint) width, (GLint) height);
2116
glMatrixMode(GL_PROJECTION);
2118
gluPerspective (30.0, 1/h, 1.0, 100.0);
2120
glMatrixMode(GL_MODELVIEW);
2122
gluLookAt( 0.0, 0.0, 30.0,
2126
glClear(GL_COLOR_BUFFER_BIT);
2129
GLfloat render_width, layout_width;
2130
pp->vp_height = 1.0;
2133
pp->vp_left = -pp->vp_width/2;
2134
pp->vp_right = pp->vp_width/2;
2135
pp->vp_top = pp->vp_height/2;
2136
pp->vp_bottom = -pp->vp_height/2;
2138
render_width = pp->vp_width * 2;
2139
layout_width = pp->vp_width * 0.8 * gear_size;
2141
pp->render_left = -render_width/2;
2142
pp->render_right = render_width/2;
2144
pp->layout_left = pp->render_right;
2145
pp->layout_right = pp->layout_left + layout_width;
2151
pinion_handle_event (ModeInfo *mi, XEvent *event)
2153
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2155
if (event->xany.type == ButtonPress &&
2156
event->xbutton.button == Button1)
2158
pp->button_down_p = True;
2159
gltrackball_start (pp->trackball,
2160
event->xbutton.x, event->xbutton.y,
2161
MI_WIDTH (mi), MI_HEIGHT (mi));
2164
else if (event->xany.type == ButtonRelease &&
2165
event->xbutton.button == Button1)
2167
pp->button_down_p = False;
2170
else if (event->xany.type == ButtonPress &&
2171
(event->xbutton.button == Button4 ||
2172
event->xbutton.button == Button5))
2174
gltrackball_mousewheel (pp->trackball, event->xbutton.button, 5,
2175
!!event->xbutton.state);
2178
else if (event->xany.type == MotionNotify &&
2181
gltrackball_track (pp->trackball,
2182
event->xmotion.x, event->xmotion.y,
2183
MI_WIDTH (mi), MI_HEIGHT (mi));
2186
else if (event->xany.type == KeyPress)
2190
XLookupString (&event->xkey, &c, 1, &keysym, 0);
2191
if (c == ' ' && debug_one_gear_p && pp->ngears)
2193
delete_gear (mi, pp->gears[0]);
2203
init_pinion (ModeInfo *mi)
2205
pinion_configuration *pp;
2206
int wire = MI_IS_WIREFRAME(mi);
2209
pps = (pinion_configuration *)
2210
calloc (MI_NUM_SCREENS(mi), sizeof (pinion_configuration));
2212
fprintf(stderr, "%s: out of memory\n", progname);
2216
pp = &pps[MI_SCREEN(mi)];
2219
pp = &pps[MI_SCREEN(mi)];
2221
pp->glx_context = init_GL(mi);
2224
reshape_pinion (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2226
pp->title_list = glGenLists (1);
2232
plane_displacement *= gear_size;
2236
GLfloat pos[4] = {-3.0, 1.0, 1.0, 0.0};
2237
GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0};
2238
GLfloat dif[4] = { 1.0, 1.0, 1.0, 1.0};
2239
GLfloat spc[4] = { 1.0, 1.0, 1.0, 1.0};
2241
glEnable(GL_LIGHTING);
2242
glEnable(GL_LIGHT0);
2243
glEnable(GL_DEPTH_TEST);
2244
glEnable(GL_CULL_FACE);
2246
glLightfv(GL_LIGHT0, GL_POSITION, pos);
2247
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2248
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2249
glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2252
pp->trackball = gltrackball_init ();
2259
draw_pinion (ModeInfo *mi)
2261
pinion_configuration *pp = &pps[MI_SCREEN(mi)];
2262
Display *dpy = MI_DISPLAY(mi);
2263
Window window = MI_WINDOW(mi);
2264
Bool wire_p = MI_IS_WIREFRAME(mi);
2265
static int tick = 0;
2267
if (!pp->glx_context)
2270
if (!pp->button_down_p)
2272
if (!debug_one_gear_p || pp->ngears == 0)
2277
glShadeModel(GL_SMOOTH);
2279
glEnable(GL_DEPTH_TEST);
2280
glEnable(GL_NORMALIZE);
2281
glEnable(GL_CULL_FACE);
2283
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2287
gltrackball_rotate (pp->trackball);
2288
mi->polygon_count = 0;
2290
glScalef (16, 16, 16); /* map vp_width/height to the screen */
2292
if (debug_one_gear_p) /* zoom in */
2294
else if (debug_p) /* show the "visible" and "layout" areas */
2296
GLfloat ow = pp->layout_right - pp->render_left;
2297
GLfloat rw = pp->render_right - pp->render_left;
2298
GLfloat s = (pp->vp_width / ow) * 0.85;
2300
glTranslatef (-(ow - rw) / 2, 0, 0);
2305
glScalef (s, s, s); /* zoom in a little more */
2306
glRotatef (-35, 1, 0, 0); /* tilt back */
2307
glRotatef ( 8, 0, 1, 0); /* tilt left */
2308
glTranslatef (0.02, 0.1, 0); /* pan up */
2315
if (!wire_p) glDisable(GL_LIGHTING);
2316
glColor3f (0.6, 0, 0);
2317
glBegin(GL_LINE_LOOP);
2318
glVertex3f (pp->render_left, pp->vp_top, 0);
2319
glVertex3f (pp->render_right, pp->vp_top, 0);
2320
glVertex3f (pp->render_right, pp->vp_bottom, 0);
2321
glVertex3f (pp->render_left, pp->vp_bottom, 0);
2323
glColor3f (0.4, 0, 0);
2325
glVertex3f (pp->vp_left, pp->vp_top, 0);
2326
glVertex3f (pp->vp_left, pp->vp_bottom, 0);
2327
glVertex3f (pp->vp_right, pp->vp_top, 0);
2328
glVertex3f (pp->vp_right, pp->vp_bottom, 0);
2330
glColor3f (0, 0.4, 0);
2331
glBegin(GL_LINE_LOOP);
2332
glVertex3f (pp->layout_left, pp->vp_top, 0);
2333
glVertex3f (pp->layout_right, pp->vp_top, 0);
2334
glVertex3f (pp->layout_right, pp->vp_bottom, 0);
2335
glVertex3f (pp->layout_left, pp->vp_bottom, 0);
2337
if (!wire_p) glEnable(GL_LIGHTING);
2340
if (tick++ > 10) /* only do this every N frames */
2343
find_mouse_gear (mi);
2349
glCallList (pp->title_list);
2351
if (mi->fps_p) do_fps (mi);
2354
glXSwapBuffers(dpy, window);