From http://www.jwz.org/xscreensaver/xscreensaver-5.31.tar.gz
[xscreensaver] / hacks / glx / involute.c
1 /* involute, Copyright (c) 2004-2014 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  *
11  * Utilities for rendering OpenGL gears with involute teeth.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif /* HAVE_CONFIG_H */
17
18 #include "screenhackI.h"
19
20 #ifndef HAVE_COCOA
21 # include <GL/glx.h>
22 # include <GL/glu.h>
23 #endif /* !HAVE_COCOA */
24
25 #ifdef HAVE_JWZGLES
26 # include "jwzgles.h"
27 #endif /* HAVE_JWZGLES */
28
29 #include "involute.h"
30 #include "normals.h"
31
32 #undef countof
33 #define countof(x) (sizeof((x))/sizeof((*x)))
34
35
36 /* For debugging: if true then in wireframe, do not abbreviate. */
37 static Bool wire_all_p = False;
38 static Bool show_normals_p = False;
39
40
41 /* Draws an uncapped tube of the given radius extending from top to bottom,
42    with faces pointing either in or out.
43  */
44 static int
45 draw_ring (int segments,
46            GLfloat r, GLfloat top, GLfloat bottom, GLfloat slope,
47            Bool in_p, Bool wire_p)
48 {
49   int i;
50   int polys = 0;
51   GLfloat width = M_PI * 2 / segments;
52
53   GLfloat s1 = 1 + ((bottom-top) * slope / 2);
54   GLfloat s2 = 1 - ((bottom-top) * slope / 2);
55
56   if (top != bottom)
57     {
58       glFrontFace (in_p ? GL_CCW : GL_CW);
59       glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
60       for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
61         {
62           GLfloat th = i * width;
63           GLfloat cth = cos(th);
64           GLfloat sth = sin(th);
65           if (in_p)
66             glNormal3f (-cth, -sth, 0);
67           else
68             glNormal3f (cth, sth, 0);
69           glVertex3f (s1 * cth * r, s1 * sth * r, top);
70           glVertex3f (s2 * cth * r, s2 * sth * r, bottom);
71         }
72       polys += segments;
73       glEnd();
74     }
75
76   if (wire_p)
77     {
78       glBegin (GL_LINE_LOOP);
79       for (i = 0; i < segments; i++)
80         {
81           GLfloat th = i * width;
82           glVertex3f (cos(th) * r, sin(th) * r, top);
83         }
84       glEnd();
85       glBegin (GL_LINE_LOOP);
86       for (i = 0; i < segments; i++)
87         {
88           GLfloat th = i * width;
89           glVertex3f (cos(th) * r, sin(th) * r, bottom);
90         }
91       glEnd();
92     }
93
94   return polys;
95 }
96
97
98 /* Draws a donut-shaped disc between the given radii,
99    with faces pointing either up or down.
100    The first radius may be 0, in which case, a filled disc is drawn.
101  */
102 static int
103 draw_disc (int segments,
104            GLfloat ra, GLfloat rb, GLfloat z, 
105            Bool up_p, Bool wire_p)
106 {
107   int i;
108   int polys = 0;
109   GLfloat width = M_PI * 2 / segments;
110
111   if (ra <  0) abort();
112   if (rb <= 0) abort();
113
114   if (ra == 0)
115     glFrontFace (up_p ? GL_CW : GL_CCW);
116   else
117     glFrontFace (up_p ? GL_CCW : GL_CW);
118
119   if (ra == 0)
120     glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
121   else
122     glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
123
124   glNormal3f (0, 0, (up_p ? -1 : 1));
125
126   if (ra == 0 && !wire_p)
127     glVertex3f (0, 0, z);
128
129   for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
130     {
131       GLfloat th = i * width;
132       GLfloat cth = cos(th);
133       GLfloat sth = sin(th);
134       if (wire_p || ra != 0)
135         glVertex3f (cth * ra, sth * ra, z);
136       glVertex3f (cth * rb, sth * rb, z);
137     }
138   polys += segments;
139   glEnd();
140   return polys;
141 }
142
143
144 /* Draws N thick radial lines between the given radii,
145    with faces pointing either up or down.
146  */
147 static int
148 draw_spokes (int n, GLfloat thickness, int segments,
149              GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2, GLfloat slope,
150              Bool wire_p)
151 {
152   int i;
153   int polys = 0;
154   GLfloat width;
155   int segments2 = 0;
156   int insegs, outsegs;
157   int tick;
158   int state;
159
160   GLfloat s1 = 1 + ((z2-z1) * slope / 2);
161   GLfloat s2 = 1 - ((z2-z1) * slope / 2);
162
163   if (ra <= 0 || rb <= 0) abort();
164
165   segments *= 3;
166
167   while (segments2 < segments) /* need a multiple of N >= segments */
168     segments2 += n;            /* (yes, this is a moronic way to find that) */
169
170   insegs  = ((float) (segments2 / n) + 0.5) / thickness;
171   outsegs = (segments2 / n) - insegs;
172   if (insegs  <= 0) insegs = 1;
173   if (outsegs <= 0) outsegs = 1;
174
175   segments2 = (insegs + outsegs) * n;
176   width = M_PI * 2 / segments2;
177
178   tick = 0;
179   state = 0;
180   for (i = 0; i < segments2; i++, tick++)
181     {
182       GLfloat th1 = i * width;
183       GLfloat th2 = th1 + width;
184       GLfloat cth1 = cos(th1);
185       GLfloat sth1 = sin(th1);
186       GLfloat cth2 = cos(th2);
187       GLfloat sth2 = sin(th2);
188       GLfloat orb = rb;
189
190       int changed = (i == 0);
191
192       if (state == 0 && tick == insegs)
193         tick = 0, state = 1, changed = 1;
194       else if (state == 1 && tick == outsegs)
195         tick = 0, state = 0, changed = 1;
196
197       if ((state == 1 ||                /* in */
198            (state == 0 && changed)) &&
199           (!wire_p || wire_all_p))
200         {
201           /* top */
202           glFrontFace (GL_CCW);
203           glBegin (wire_p ? GL_LINES : GL_QUADS);
204           glNormal3f (0, 0, -1);
205           glVertex3f (s1 * cth1 * ra, s1 * sth1 * ra, z1);
206           glVertex3f (s1 * cth1 * rb, s1 * sth1 * rb, z1);
207           glVertex3f (s1 * cth2 * rb, s1 * sth2 * rb, z1);
208           glVertex3f (s1 * cth2 * ra, s1 * sth2 * ra, z1);
209           polys++;
210           glEnd();
211
212           /* bottom */
213           glFrontFace (GL_CW);
214           glBegin (wire_p ? GL_LINES : GL_QUADS);
215           glNormal3f (0, 0, 1);
216           glVertex3f (s2 * cth1 * ra, s2 * sth1 * ra, z2);
217           glVertex3f (s2 * cth1 * rb, s2 * sth1 * rb, z2);
218           glVertex3f (s2 * cth2 * rb, s2 * sth2 * rb, z2);
219           glVertex3f (s2 * cth2 * ra, s2 * sth2 * ra, z2);
220           polys++;
221           glEnd();
222         }
223
224       if (state == 1 && changed)   /* entering "in" state */
225         {
226           /* left */
227           glFrontFace (GL_CW);
228           glBegin (wire_p ? GL_LINES : GL_QUADS);
229           do_normal (s1 * cth1 * rb, s1 * sth1 * rb, z1,
230                      s1 * cth1 * ra, s1 * sth1 * ra, z1,
231                      s2 * cth1 * rb, s2 * sth1 * rb, z2);
232           glVertex3f (s1 * cth1 * ra, s1 * sth1 * ra, z1);
233           glVertex3f (s1 * cth1 * rb, s1 * sth1 * rb, z1);
234           glVertex3f (s2 * cth1 * rb, s2 * sth1 * rb, z2);
235           glVertex3f (s2 * cth1 * ra, s2 * sth1 * ra, z2);
236           polys++;
237           glEnd();
238         }
239
240       if (state == 0 && changed)   /* entering "out" state */
241         {
242           /* right */
243           glFrontFace (GL_CCW);
244           glBegin (wire_p ? GL_LINES : GL_QUADS);
245           do_normal (s1 * cth2 * ra, s1 * sth2 * ra, z1,
246                      s1 * cth2 * rb, s1 * sth2 * rb, z1,
247                      s2 * cth2 * rb, s2 * sth2 * rb, z2);
248           glVertex3f (s1 * cth2 * ra, s1 * sth2 * ra, z1);
249           glVertex3f (s1 * cth2 * rb, s1 * sth2 * rb, z1);
250           glVertex3f (s2 * cth2 * rb, s2 * sth2 * rb, z2);
251           glVertex3f (s2 * cth2 * ra, s2 * sth2 * ra, z2);
252           polys++;
253           glEnd();
254         }
255
256       rb = orb;
257     }
258   return polys;
259 }
260
261
262 /* Draws some bumps (embedded cylinders) on the gear.
263  */
264 static int
265 draw_gear_nubs (gear *g, Bool wire_p)
266 {
267   int polys = 0;
268   int i;
269   int steps = (g->size < INVOLUTE_LARGE ? 5 : 20);
270   double r, size, height;
271   GLfloat *cc;
272   int which;
273   GLfloat width, off;
274
275   if (! g->nubs) return 0;
276
277   which = involute_biggest_ring (g, &r, &size, &height);
278   size /= 5;
279   height *= 0.7;
280
281   cc = (which == 1 ? g->color : g->color2);
282   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cc);
283
284   if (g->inverted_p)
285     r = g->r + size + g->tooth_h;
286
287   width = M_PI * 2 / g->nubs;
288   off = M_PI / (g->nteeth * 2);  /* align first nub with a tooth */
289
290   for (i = 0; i < g->nubs; i++)
291     {
292       GLfloat th = (i * width) + off;
293       glPushMatrix();
294
295       glRotatef (th * 180 / M_PI, 0, 0, 1);
296       glTranslatef (r, 0, 0);
297
298       if (g->inverted_p)        /* nubs go on the outside rim */
299         {
300           size = g->thickness / 3;
301           height = (g->r - g->inner_r)/2;
302           glTranslatef (height, 0, 0);
303           glRotatef (90, 0, 1, 0);
304         }
305
306       if (wire_p && !wire_all_p)
307         polys += draw_ring ((g->size >= INVOLUTE_LARGE ? steps/2 : steps),
308                             size, 0, 0, 0, False, wire_p);
309       else
310         {
311           polys += draw_disc (steps, 0, size, -height,      True,  wire_p);
312           polys += draw_disc (steps, 0, size,  height,      False, wire_p);
313           polys += draw_ring (steps, size, -height, height, 0, False, wire_p);
314         }
315       glPopMatrix();
316     }
317   return polys;
318 }
319
320
321
322 /* Draws a much simpler representation of a gear.
323    Returns the number of polygons.
324  */
325 int
326 draw_involute_schematic (gear *g, Bool wire_p)
327 {
328   int polys = 0;
329   int i;
330   GLfloat width = M_PI * 2 / g->nteeth;
331
332   if (!wire_p) glDisable(GL_LIGHTING);
333   glColor3f (g->color[0] * 0.6, g->color[1] * 0.6, g->color[2] * 0.6);
334
335   glBegin (GL_LINES);
336   for (i = 0; i < g->nteeth; i++)
337     {
338       GLfloat th = (i * width) + (width/4);
339       glVertex3f (0, 0, -g->thickness/2);
340       glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
341     }
342   polys += g->nteeth;
343   glEnd();
344
345   glBegin (GL_LINE_LOOP);
346   for (i = 0; i < g->nteeth; i++)
347     {
348       GLfloat th = (i * width) + (width/4);
349       glVertex3f (cos(th) * g->r, sin(th) * g->r, -g->thickness/2);
350     }
351   polys += g->nteeth;
352   glEnd();
353
354   if (!wire_p) glEnable(GL_LIGHTING);
355   return polys;
356 }
357
358
359 /* Renders all the interior (non-toothy) parts of a gear:
360    the discs, axles, etc.
361  */
362 static int
363 draw_gear_interior (gear *g, Bool wire_p)
364 {
365   int polys = 0;
366
367   int steps = g->nteeth * 2;
368   if (steps < 10) steps = 10;
369   if ((wire_p && !wire_all_p) || g->size < INVOLUTE_LARGE) steps /= 2;
370   if (g->size < INVOLUTE_LARGE && steps > 16) steps = 16;
371
372   /* ring 1 (facing in) is done in draw_gear_teeth */
373
374   /* ring 2 (facing in) and disc 2
375    */
376   if (g->inner_r2)
377     {
378       GLfloat ra = g->inner_r * 1.04;  /* slightly larger than inner_r */
379       GLfloat rb = g->inner_r2;        /*  since the points don't line up */
380       GLfloat za = -g->thickness2/2;
381       GLfloat zb =  g->thickness2/2;
382       GLfloat s1 = 1 + (g->thickness2 * g->tooth_slope / 2);
383       GLfloat s2 = 1 - (g->thickness2 * g->tooth_slope / 2);
384
385       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color2);
386
387       if ((g->coax_p != 1 && !g->inner_r3) ||
388           (wire_p && wire_all_p))
389         polys +=   /* ring facing in */
390           draw_ring (steps, rb, za, zb, g->tooth_slope, True, wire_p);
391
392       if (wire_p && wire_all_p)
393         polys +=  /* ring facing in */
394           draw_ring (steps, ra, za, zb, g->tooth_slope, True, wire_p);
395
396       if (g->spokes)
397         polys += draw_spokes (g->spokes, g->spoke_thickness,
398                               steps, ra, rb, za, zb, g->tooth_slope, wire_p);
399       else if (!wire_p || wire_all_p)
400         {
401           polys +=   /* top plate */
402             draw_disc (steps, s1*ra, s1*rb, za, True, wire_p);
403           polys +=   /* bottom plate */
404             draw_disc (steps, s2*ra, s2*rb, zb, False, wire_p);
405         }
406     }
407
408   /* ring 3 (facing in and out) and disc 3
409    */
410   if (g->inner_r3)
411     {
412       GLfloat ra = g->inner_r2;
413       GLfloat rb = g->inner_r3;
414       GLfloat za = -g->thickness3/2;
415       GLfloat zb =  g->thickness3/2;
416       GLfloat s1 = 1 + (g->thickness3 * g->tooth_slope / 2);
417       GLfloat s2 = 1 - (g->thickness3 * g->tooth_slope / 2);
418
419       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
420
421       polys +=   /* ring facing out */
422         draw_ring (steps, ra, za, zb, g->tooth_slope, False, wire_p);
423
424       if (g->coax_p != 1 || (wire_p && wire_all_p))
425         polys += /* ring facing in */
426           draw_ring (steps, rb, za, zb, g->tooth_slope, True, wire_p);
427
428       if (!wire_p || wire_all_p)
429         {
430           polys +=   /* top plate */
431             draw_disc (steps, s1*ra, s1*rb, za, True, wire_p);
432           polys +=  /* bottom plate */
433             draw_disc (steps, s2*ra, s2*rb, zb, False, wire_p);
434         }
435     }
436
437   /* axle tube
438    */
439   if (g->coax_p == 1)
440     {
441       GLfloat cap_height = g->coax_thickness/3;
442
443       GLfloat ra = (g->inner_r3 ? g->inner_r3 :
444                     g->inner_r2 ? g->inner_r2 :
445                     g->inner_r);
446       GLfloat za = -(g->thickness/2 + cap_height);
447       GLfloat zb = g->coax_thickness/2 + g->coax_displacement + cap_height;
448
449       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
450
451       if (wire_p && !wire_all_p) steps /= 2;
452
453       polys += 
454         draw_ring (steps, ra, za, zb, g->tooth_slope, False, wire_p);
455
456       if (!wire_p || wire_all_p)
457         {
458           polys += 
459             draw_disc (steps, 0,  ra, za, True, wire_p);  /* top plate */
460           polys +=
461             draw_disc (steps, 0,  ra, zb, False, wire_p); /* bottom plate */
462         }
463     }
464   return polys;
465 }
466
467
468 /* gear_teeth_geometry computes the vertices and normals of the teeth
469    of a gear.  This is the heavy lifting: there are a ton of polygons
470    around the perimiter of a gear, and the normals are difficult (not
471    radial or right angles.)
472
473    It would be nice if we could cache this, but the numbers are
474    different for essentially every gear:
475
476       - Every gear has a different inner_r, so the vertices of the
477         inner ring (and thus, the triangle fans on the top and bottom
478         faces) are different in a non-scalable way.
479
480       - If the ratio between tooth_w and tooth_h changes, the normals
481         on the outside edges of the teeth are invalid (this can happen
482         every time we start a new train.)
483
484    So instead, we rely on OpenGL display lists to do the cacheing for
485    us -- we only compute all these normals once per gear, instead of
486    once per gear per frame.
487  */
488
489 typedef struct {
490   int npoints;
491   XYZ *points;
492   XYZ *fnormals;  /* face normals */
493   XYZ *pnormals;  /* point normals */
494 } tooth_face;
495
496
497 static void
498 tooth_normals (tooth_face *f, GLfloat tooth_slope)
499 {
500   int i;
501
502   /* Compute the face normals for each facet. */
503   for (i = 0; i < f->npoints; i++)
504     {
505       XYZ p1, p2, p3;
506       int a = i;
507       int b = (i == f->npoints-1 ? 0 : i+1);
508       p1 = f->points[a];
509       p2 = f->points[b];
510       p3 = p1;
511       p3.x -= (p3.x * tooth_slope);
512       p3.y -= (p3.y * tooth_slope);
513       p3.z++;
514       f->fnormals[i] = calc_normal (p1, p2, p3);
515     }
516
517   /* From the face normals, compute the vertex normals
518      (by averaging the normals of adjascent faces.)
519    */
520   for (i = 0; i < f->npoints; i++)
521     {
522       int a = (i == 0 ? f->npoints-1 : i-1);
523       int b = i;
524       XYZ n1 = f->fnormals[a];   /* normal of [i-1 - i] face */
525       XYZ n2 = f->fnormals[b];   /* normal of [i - i+1] face */
526       f->pnormals[i].x = (n1.x + n2.x) / 2;
527       f->pnormals[i].y = (n1.y + n2.y) / 2;
528       f->pnormals[i].z = (n1.z + n2.z) / 2;
529     }
530 }
531
532
533 static void
534 gear_teeth_geometry (gear *g,
535                      tooth_face *orim,      /* outer rim (the teeth) */
536                      tooth_face *irim)      /* inner rim (the hole)  */
537 {
538   int i;
539   int ppt = 20;   /* max points per tooth */
540   GLfloat width = M_PI * 2 / g->nteeth;
541   GLfloat rh = g->tooth_h;
542   GLfloat tw = width;
543
544   /* Approximate shape of an "involute" gear tooth.
545
546                                  (TH)
547                  th0 th2 th4 th6 th8 t10 t12 t14 th16  th18   th20
548                    :  :  :   :    :    :   :  :  :      :      :
549                    :  :  :   :    :    :   :  :  :      :      :
550         r0 ........:..:..:...___________...:..:..:......:......:..
551                    :  :  :  /:    :    :\  :  :  :      :      :
552                    :  :  : / :    :    : \ :  :  :      :      :
553                    :  :  :/  :    :    :  \:  :  :      :      :
554         r2 ........:.....@...:....:....:...@..:..:......:......:..
555                    :  : @:   :    :    :   :@ :  :      :      :
556     (R) ...........:...@.:...:....:....:...:.@..........:......:......
557                    :  :@ :   :    :    :   : @:  :      :      :
558         r4 ........:..@..:...:....:....:...:..@:........:......:..
559                    : /:  :   :    :    :   :  :\ :      :      :
560                    :/ :  :   :    :    :   :  : \:      :      : /
561         r6 ......__/..:..:...:....:....:...:..:..\______________/
562                    :  :  :   :    :    :   :  :  :      :      :
563                    |  :  :   :    :    :   :  :  |      :      :
564                    :  :  :   :    :    :   :  :  :      :      :
565                    |  :  :   :    :    :   :  :  |      :      :
566         r8 ......__:_____________________________:________________
567    */
568
569   GLfloat r[30];
570   GLfloat th[30];
571   GLfloat R = g->r;
572
573   r[0] = R + (rh * 0.50);
574   r[1] = R + (rh * 0.40);
575   r[2] = R + (rh * 0.25);
576   r[3] = R + (rh * 0.05);
577   r[4] = R - (r[2]-R);
578   r[5] = R - (r[1]-R);
579   r[6] = R - (r[0]-R);
580   r[7] = r[6]; /* unused */
581   r[8] = g->inner_r;
582
583   th[0]  = -tw * (g->size == INVOLUTE_SMALL ? 0.5 : 
584                   g->size == INVOLUTE_MEDIUM ? 0.41 : 0.45);
585   th[1]  = -tw * 0.375;
586   th[2]  = -tw * 0.300;
587   th[3]  = -tw * 0.230;
588   th[4]  = -tw * (g->nteeth >= 5 ? 0.16 : 0.12);
589   th[5]  = -tw * 0.100;
590   th[6]  = -tw * (g->size == INVOLUTE_MEDIUM ? 0.1 : 0.04);
591   th[7]  = -tw * 0.020;
592   th[8]  =  0;
593   th[9]  = -th[7];
594   th[10] = -th[6];
595   th[11] = -th[5];
596   th[12] = -th[4];
597   th[13] = -th[3];
598   th[14] = -th[2];
599   th[15] = -th[1];
600   th[16] = -th[0];
601   th[17] =  width * 0.47;
602   th[18] =  width * 0.50;
603   th[19] =  width * 0.53;
604   th[20] =  th[0] + width; /* unused */
605
606   if (g->inverted_p)   /* put the teeth on the inside */
607     {
608       for (i = 0; i < countof(th); i++)
609         th[i] = -th[i];
610       for (i = 0; i < countof(r); i++)
611         r[i] = R - (r[i] - R);
612     }
613
614   orim->npoints  = 0;
615   orim->points   = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->points));
616   orim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->fnormals));
617   orim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*orim->pnormals));
618
619   irim->npoints  = 0;
620   irim->points   = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->points));
621   irim->fnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->fnormals));
622   irim->pnormals = (XYZ *) calloc(ppt * g->nteeth+1, sizeof(*irim->pnormals));
623
624   if (!orim->points || !orim->pnormals || !orim->fnormals ||
625       !irim->points || !irim->pnormals || !irim->fnormals)
626     {
627       fprintf (stderr, "%s: out of memory\n", progname);
628       exit (1);
629     }
630
631   /* First, compute the coordinates of every point used for every tooth.
632    */
633   for (i = 0; i < g->nteeth; i++)
634     {
635       GLfloat TH = (i * width) + (width/4);
636       int oon = orim->npoints;
637       int oin = irim->npoints;
638
639 #     undef PUSH
640 #     define PUSH(OPR,IPR,PTH) \
641         orim->points[orim->npoints].x = cos(TH+th[(PTH)]) * r[(OPR)]; \
642         orim->points[orim->npoints].y = sin(TH+th[(PTH)]) * r[(OPR)]; \
643         orim->npoints++; \
644         irim->points[irim->npoints].x = cos(TH+th[(PTH)]) * r[(IPR)]; \
645         irim->points[irim->npoints].y = sin(TH+th[(PTH)]) * r[(IPR)]; \
646         irim->npoints++
647
648       switch (g->size) {
649       case INVOLUTE_SMALL:
650         PUSH(6, 8, 0);       /* tooth left 1 */
651         PUSH(0, 8, 8);       /* tooth top middle */
652         break;
653       case INVOLUTE_MEDIUM:
654         PUSH(6, 8, 0);       /* tooth left 1 */
655         PUSH(0, 8, 6);       /* tooth top left */
656         PUSH(0, 8, 10);      /* tooth top right */
657         PUSH(6, 8, 16);      /* tooth right 6 */
658         break;
659       case INVOLUTE_LARGE:
660         PUSH(6, 8, 0);       /* tooth left 1 */
661         PUSH(4, 8, 2);       /* tooth left 3 */
662         PUSH(2, 8, 4);       /* tooth left 5 */
663         PUSH(0, 8, 6);       /* tooth top left */
664         PUSH(0, 8, 10);      /* tooth top right */
665         PUSH(2, 8, 12);      /* tooth right 1 */
666         PUSH(4, 8, 14);      /* tooth right 3 */
667         PUSH(6, 8, 16);      /* tooth right 5 */
668         PUSH(6, 8, 18);      /* gap top */
669         break;
670       case INVOLUTE_HUGE:
671         PUSH(6, 8, 0);       /* tooth left 1 */
672         PUSH(5, 8, 1);       /* tooth left 2 */
673         PUSH(4, 8, 2);       /* tooth left 3 */
674         PUSH(3, 8, 3);       /* tooth left 4 */
675         PUSH(2, 8, 4);       /* tooth left 5 */
676         PUSH(1, 8, 5);       /* tooth left 6 */
677         PUSH(0, 8, 6);       /* tooth top left */
678         PUSH(0, 8, 8);       /* tooth top left */
679         PUSH(0, 8, 10);      /* tooth top right */
680         PUSH(1, 8, 11);      /* tooth top right */
681         PUSH(2, 8, 12);      /* tooth right 1 */
682         PUSH(3, 8, 13);      /* tooth right 2 */
683         PUSH(4, 8, 14);      /* tooth right 3 */
684         PUSH(5, 8, 15);      /* tooth right 4 */
685         PUSH(6, 8, 16);      /* tooth right 5 */
686         PUSH(6, 8, 17);      /* tooth right 6 */
687         PUSH(6, 8, 18);      /* gap top */
688         PUSH(6, 8, 19);      /* gap top */
689         break;
690       default:
691         abort();
692       }
693 #     undef PUSH
694
695       if (i == 0 && orim->npoints > ppt) abort();  /* go update "ppt"! */
696
697       if (g->inverted_p)
698         {
699           int start, end, j;
700           start = oon;
701           end = orim->npoints;
702           for (j = 0; j < (end-start)/2; j++)
703             {
704               XYZ swap = orim->points[end-j-1];
705               orim->points[end-j-1] = orim->points[start+j];
706               orim->points[start+j] = swap;
707             }
708
709           start = oin;
710           end = irim->npoints;
711           for (j = 0; j < (end-start)/2; j++)
712             {
713               XYZ swap = irim->points[end-j-1];
714               irim->points[end-j-1] = irim->points[start+j];
715               irim->points[start+j] = swap;
716             }
717         }
718     }
719
720   tooth_normals (orim, g->tooth_slope);
721   tooth_normals (irim, 0);
722
723   if (g->inverted_p)   /* flip the normals */
724     {
725       for (i = 0; i < orim->npoints; i++)
726         {
727           orim->fnormals[i].x = -orim->fnormals[i].x;
728           orim->fnormals[i].y = -orim->fnormals[i].y;
729           orim->fnormals[i].z = -orim->fnormals[i].z;
730
731           orim->pnormals[i].x = -orim->pnormals[i].x;
732           orim->pnormals[i].y = -orim->pnormals[i].y;
733           orim->pnormals[i].z = -orim->pnormals[i].z;
734         }
735
736       for (i = 0; i < irim->npoints; i++)
737         {
738           irim->fnormals[i].x = -irim->fnormals[i].x;
739           irim->fnormals[i].y = -irim->fnormals[i].y;
740           irim->fnormals[i].z = -irim->fnormals[i].z;
741
742           irim->pnormals[i].x = -irim->pnormals[i].x;
743           irim->pnormals[i].y = -irim->pnormals[i].y;
744           irim->pnormals[i].z = -irim->pnormals[i].z;
745         }
746     }
747 }
748
749
750 /* Which of the gear's inside rings is the biggest? 
751  */
752 int
753 involute_biggest_ring (gear *g, double *posP, double *sizeP, double *heightP)
754 {
755   double r0 = (g->r - g->tooth_h/2);
756   double r1 = g->inner_r;
757   double r2 = g->inner_r2;
758   double r3 = g->inner_r3;
759   double w1 = (r1 ? r0 - r1 : r0);
760   double w2 = (r2 ? r1 - r2 : 0);
761   double w3 = (r3 ? r2 - r3 : 0);
762   double h1 = g->thickness;
763   double h2 = g->thickness2;
764   double h3 = g->thickness3;
765
766   if (g->spokes) w2 = 0;
767
768   if (w1 > w2 && w1 > w3)
769     {
770       if (posP)    *posP = (r0+r1)/2;
771       if (sizeP)   *sizeP = w1;
772       if (heightP) *heightP = h1;
773       return 0;
774     }
775   else if (w2 > w1 && w2 > w3)
776     {
777       if (posP)  *posP = (r1+r2)/2;
778       if (sizeP) *sizeP = w2;
779       if (heightP) *heightP = h2;
780       return 1;
781     }
782   else
783     {
784       if (posP)  *posP = (r2+r3)/2;
785       if (sizeP) *sizeP = w3;
786       if (heightP) *heightP = h3;
787       return 1;
788     }
789 }
790
791
792 /* Renders all teeth of a gear.
793  */
794 static int
795 draw_gear_teeth (gear *g, Bool wire_p)
796 {
797   int polys = 0;
798   int i;
799
800   GLfloat z1 = -g->thickness/2;
801   GLfloat z2 =  g->thickness/2;
802   GLfloat s1 = 1 + (g->thickness * g->tooth_slope / 2);
803   GLfloat s2 = 1 - (g->thickness * g->tooth_slope / 2);
804
805   tooth_face orim, irim;
806   gear_teeth_geometry (g, &orim, &irim);
807
808   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
809
810   /* Draw the outer rim (the teeth)
811      (In wire mode, this draws just the upright lines.)
812    */
813   glFrontFace (g->inverted_p ? GL_CCW : GL_CW);
814   glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
815   for (i = 0; i < orim.npoints; i++)
816     {
817       glNormal3f (orim.pnormals[i].x, orim.pnormals[i].y, orim.pnormals[i].z);
818       glVertex3f (s1*orim.points[i].x, s1*orim.points[i].y, z1);
819       glVertex3f (s2*orim.points[i].x, s2*orim.points[i].y, z2);
820
821       /* Show the face normal vectors */
822       if (0&&wire_p && show_normals_p)
823         {
824           XYZ n = orim.fnormals[i];
825           int a = i;
826           int b = (i == orim.npoints-1 ? 0 : i+1);
827           GLfloat x = (orim.points[a].x + orim.points[b].x) / 2;
828           GLfloat y = (orim.points[a].y + orim.points[b].y) / 2;
829           GLfloat z = (z1 + z2) / 2;
830           glVertex3f (x, y, z);
831           glVertex3f (x + n.x, y + n.y, z + n.z);
832         }
833
834       /* Show the vertex normal vectors */
835       if (wire_p && show_normals_p)
836         {
837           XYZ n = orim.pnormals[i];
838           GLfloat x = orim.points[i].x;
839           GLfloat y = orim.points[i].y;
840           GLfloat z = (z1 + z2) / 2;
841           glVertex3f (x, y, z);
842           glVertex3f (x + n.x, y + n.y, z + n.z);
843         }
844     }
845
846   if (!wire_p)  /* close the quad loop */
847     {
848       glNormal3f (orim.pnormals[0].x, orim.pnormals[0].y, orim.pnormals[0].z);
849       glVertex3f (s1*orim.points[0].x, s1*orim.points[0].y, z1);
850       glVertex3f (s2*orim.points[0].x, s2*orim.points[0].y, z2);
851     }
852   polys += orim.npoints;
853   glEnd();
854
855   /* Draw the outer rim circles, in wire mode */
856   if (wire_p)
857     {
858       glBegin (GL_LINE_LOOP);
859       for (i = 0; i < orim.npoints; i++)
860         glVertex3f (s1*orim.points[i].x, s1*orim.points[i].y, z1);
861       glEnd();
862       glBegin (GL_LINE_LOOP);
863       for (i = 0; i < orim.npoints; i++)
864         glVertex3f (s2*orim.points[i].x, s2*orim.points[i].y, z2);
865       glEnd();
866     }
867
868   /* Draw the inner rim (the hole)
869      (In wire mode, this draws just the upright lines.)
870    */
871   glFrontFace (g->inverted_p ? GL_CW : GL_CCW);
872   glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
873   for (i = 0; i < irim.npoints; i++)
874     {
875       glNormal3f(-irim.pnormals[i].x, -irim.pnormals[i].y,-irim.pnormals[i].z);
876       glVertex3f (s1*irim.points[i].x, s1*irim.points[i].y, z1);
877       glVertex3f (s2*irim.points[i].x, s2*irim.points[i].y, z2);
878
879       /* Show the face normal vectors */
880       if (wire_p && show_normals_p)
881         {
882           XYZ n = irim.fnormals[i];
883           int a = i;
884           int b = (i == irim.npoints-1 ? 0 : i+1);
885           GLfloat x = (irim.points[a].x + irim.points[b].x) / 2;
886           GLfloat y = (irim.points[a].y + irim.points[b].y) / 2;
887           GLfloat z  = (z1 + z2) / 2;
888           glVertex3f (x, y, z);
889           glVertex3f (x - n.x, y - n.y, z);
890         }
891
892       /* Show the vertex normal vectors */
893       if (wire_p && show_normals_p)
894         {
895           XYZ n = irim.pnormals[i];
896           GLfloat x = irim.points[i].x;
897           GLfloat y = irim.points[i].y;
898           GLfloat z = (z1 + z2) / 2;
899           glVertex3f (x, y, z);
900           glVertex3f (x - n.x, y - n.y, z);
901         }
902     }
903
904   if (!wire_p)  /* close the quad loop */
905     {
906       glNormal3f (-irim.pnormals[0].x,-irim.pnormals[0].y,-irim.pnormals[0].z);
907       glVertex3f (s1*irim.points[0].x, s1*irim.points[0].y, z1);
908       glVertex3f (s2*irim.points[0].x, s2*irim.points[0].y, z2);
909     }
910   polys += irim.npoints;
911   glEnd();
912
913   /* Draw the inner rim circles, in wire mode
914    */
915   if (wire_p)
916     {
917       glBegin (GL_LINE_LOOP);
918       for (i = 0; i < irim.npoints; i++)
919         glVertex3f (irim.points[i].x, irim.points[i].y, z1);
920       glEnd();
921       glBegin (GL_LINE_LOOP);
922       for (i = 0; i < irim.npoints; i++)
923         glVertex3f (irim.points[i].x, irim.points[i].y, z2);
924       glEnd();
925     }
926
927   /* Draw the side (the flat bit)
928    */
929   if (!wire_p || wire_all_p)
930     {
931       GLfloat z;
932       if (irim.npoints != orim.npoints) abort();
933       for (z = z1; z <= z2; z += z2-z1)
934         {
935           GLfloat s = (z == z1 ? s1 : s2);
936           glFrontFace (((z == z1) ^ g->inverted_p) ? GL_CCW : GL_CW);
937           glNormal3f (0, 0, z);
938           glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
939           for (i = 0; i < orim.npoints; i++)
940             {
941               glVertex3f (s*orim.points[i].x, s*orim.points[i].y, z);
942               glVertex3f (s*irim.points[i].x, s*irim.points[i].y, z);
943             }
944           if (!wire_p)  /* close the quad loop */
945             {
946               glVertex3f (s*orim.points[0].x, s*orim.points[0].y, z);
947               glVertex3f (s*irim.points[0].x, s*irim.points[0].y, z);
948             }
949           polys += orim.npoints;
950           glEnd();
951         }
952     }
953
954   free (irim.points);
955   free (irim.fnormals);
956   free (irim.pnormals);
957
958   free (orim.points);
959   free (orim.fnormals);
960   free (orim.pnormals);
961
962   return polys;
963 }
964
965
966 /* Render one gear, unrotated at 0,0.
967    Returns the number of polygons.
968  */
969 int
970 draw_involute_gear (gear *g, Bool wire_p)
971 {
972   int polys = 0;
973
974   static const GLfloat spec[4] = {1.0, 1.0, 1.0, 1.0};
975   GLfloat shiny   = 128.0;
976
977   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
978   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
979   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, g->color);
980   glColor3f (g->color[0], g->color[1], g->color[2]);
981
982   if (wire_p > 1)
983     polys += draw_involute_schematic (g, wire_p);
984   else
985     {
986       glPushMatrix();
987       glRotatef (g->wobble, 1, 0, 0);
988       polys += draw_gear_teeth (g, wire_p);
989       polys += draw_gear_interior (g, wire_p);
990       polys += draw_gear_nubs (g, wire_p);
991       glPopMatrix();
992     }
993   return polys;
994 }