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