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