6a132e003fa7db4300a9c81bc7ea844d223554d0
[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 #ifdef HAVE_COCOA
21 # include <OpenGL/gl.h>
22 # include <OpenGL/glu.h>
23 #else  /* !HAVE_COCOA -- real Xlib */
24 # include <GL/glx.h>
25 # include <GL/glu.h>
26 #endif /* !HAVE_COCOA */
27
28 #include "involute.h"
29 #include "normals.h"
30
31 #undef countof
32 #define countof(x) (sizeof((x))/sizeof((*x)))
33
34
35 /* For debugging: if true then in wireframe, do not abbreviate. */
36 static Bool wire_all_p = False+1;
37 static Bool show_normals_p = False+1;
38
39
40 /* Draws an uncapped tube of the given radius extending from top to bottom,
41    with faces pointing either in or out.
42  */
43 static int
44 draw_ring (int segments,
45            GLfloat r, GLfloat top, GLfloat bottom, 
46            Bool in_p, Bool wire_p)
47 {
48   int i;
49   int polys = 0;
50   GLfloat width = M_PI * 2 / segments;
51
52   if (top != bottom)
53     {
54       glFrontFace (in_p ? GL_CCW : GL_CW);
55       glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
56       for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
57         {
58           GLfloat th = i * width;
59           GLfloat cth = cos(th);
60           GLfloat sth = sin(th);
61           if (in_p)
62             glNormal3f (-cth, -sth, 0);
63           else
64             glNormal3f (cth, sth, 0);
65           glVertex3f (cth * r, sth * r, top);
66           glVertex3f (cth * r, sth * r, bottom);
67         }
68       polys += segments;
69       glEnd();
70     }
71
72   if (wire_p)
73     {
74       glBegin (GL_LINE_LOOP);
75       for (i = 0; i < segments; i++)
76         {
77           GLfloat th = i * width;
78           glVertex3f (cos(th) * r, sin(th) * r, top);
79         }
80       glEnd();
81       glBegin (GL_LINE_LOOP);
82       for (i = 0; i < segments; i++)
83         {
84           GLfloat th = i * width;
85           glVertex3f (cos(th) * r, sin(th) * r, bottom);
86         }
87       glEnd();
88     }
89
90   return polys;
91 }
92
93
94 /* Draws a donut-shaped disc between the given radii,
95    with faces pointing either up or down.
96    The first radius may be 0, in which case, a filled disc is drawn.
97  */
98 static int
99 draw_disc (int segments,
100            GLfloat ra, GLfloat rb, GLfloat z, 
101            Bool up_p, Bool wire_p)
102 {
103   int i;
104   int polys = 0;
105   GLfloat width = M_PI * 2 / segments;
106
107   if (ra <  0) abort();
108   if (rb <= 0) abort();
109
110   if (ra == 0)
111     glFrontFace (up_p ? GL_CW : GL_CCW);
112   else
113     glFrontFace (up_p ? GL_CCW : GL_CW);
114
115   if (ra == 0)
116     glBegin (wire_p ? GL_LINES : GL_TRIANGLE_FAN);
117   else
118     glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
119
120   glNormal3f (0, 0, (up_p ? -1 : 1));
121
122   if (ra == 0 && !wire_p)
123     glVertex3f (0, 0, z);
124
125   for (i = 0; i < segments + (wire_p ? 0 : 1); i++)
126     {
127       GLfloat th = i * width;
128       GLfloat cth = cos(th);
129       GLfloat sth = sin(th);
130       if (wire_p || ra != 0)
131         glVertex3f (cth * ra, sth * ra, z);
132       glVertex3f (cth * rb, sth * rb, z);
133     }
134   polys += segments;
135   glEnd();
136   return polys;
137 }
138
139
140 /* Draws N thick radial lines between the given radii,
141    with faces pointing either up or down.
142  */
143 static int
144 draw_spokes (int n, GLfloat thickness, int segments,
145              GLfloat ra, GLfloat rb, GLfloat z1, GLfloat z2,
146              Bool wire_p)
147 {
148   int i;
149   int polys = 0;
150   GLfloat width;
151   int segments2 = 0;
152   int insegs, outsegs;
153   int tick;
154   int state;
155
156   if (ra <= 0 || rb <= 0) abort();
157
158   segments *= 3;
159
160   while (segments2 < segments) /* need a multiple of N >= segments */
161     segments2 += n;            /* (yes, this is a moronic way to find that) */
162
163   insegs  = ((float) (segments2 / n) + 0.5) / thickness;
164   outsegs = (segments2 / n) - insegs;
165   if (insegs  <= 0) insegs = 1;
166   if (outsegs <= 0) outsegs = 1;
167
168   segments2 = (insegs + outsegs) * n;
169   width = M_PI * 2 / segments2;
170
171   tick = 0;
172   state = 0;
173   for (i = 0; i < segments2; i++, tick++)
174     {
175       GLfloat th1 = i * width;
176       GLfloat th2 = th1 + width;
177       GLfloat cth1 = cos(th1);
178       GLfloat sth1 = sin(th1);
179       GLfloat cth2 = cos(th2);
180       GLfloat sth2 = sin(th2);
181       GLfloat orb = rb;
182
183       int changed = (i == 0);
184
185       if (state == 0 && tick == insegs)
186         tick = 0, state = 1, changed = 1;
187       else if (state == 1 && tick == outsegs)
188         tick = 0, state = 0, changed = 1;
189
190       if ((state == 1 ||                /* in */
191            (state == 0 && changed)) &&
192           (!wire_p || wire_all_p))
193         {
194           /* top */
195           glFrontFace (GL_CCW);
196           glBegin (wire_p ? GL_LINES : GL_QUADS);
197           glNormal3f (0, 0, -1);
198           glVertex3f (cth1 * ra, sth1 * ra, z1);
199           glVertex3f (cth1 * rb, sth1 * rb, z1);
200           glVertex3f (cth2 * rb, sth2 * rb, z1);
201           glVertex3f (cth2 * ra, sth2 * ra, z1);
202           polys++;
203           glEnd();
204
205           /* bottom */
206           glFrontFace (GL_CW);
207           glBegin (wire_p ? GL_LINES : GL_QUADS);
208           glNormal3f (0, 0, 1);
209           glVertex3f (cth1 * ra, sth1 * ra, z2);
210           glVertex3f (cth1 * rb, sth1 * rb, z2);
211           glVertex3f (cth2 * rb, sth2 * rb, z2);
212           glVertex3f (cth2 * ra, sth2 * ra, z2);
213           polys++;
214           glEnd();
215         }
216
217       if (state == 1 && changed)   /* entering "in" state */
218         {
219           /* left */
220           glFrontFace (GL_CW);
221           glBegin (wire_p ? GL_LINES : GL_QUADS);
222           do_normal (cth1 * rb, sth1 * rb, z1,
223                      cth1 * ra, sth1 * ra, z1,
224                      cth1 * rb, sth1 * rb, z2);
225           glVertex3f (cth1 * ra, sth1 * ra, z1);
226           glVertex3f (cth1 * rb, sth1 * rb, z1);
227           glVertex3f (cth1 * rb, sth1 * rb, z2);
228           glVertex3f (cth1 * ra, sth1 * ra, z2);
229           polys++;
230           glEnd();
231         }
232
233       if (state == 0 && changed)   /* entering "out" state */
234         {
235           /* right */
236           glFrontFace (GL_CCW);
237           glBegin (wire_p ? GL_LINES : GL_QUADS);
238           do_normal (cth2 * ra, sth2 * ra, z1,
239                      cth2 * rb, sth2 * rb, z1,
240                      cth2 * rb, sth2 * rb, z2);
241           glVertex3f (cth2 * ra, sth2 * ra, z1);
242           glVertex3f (cth2 * rb, sth2 * rb, z1);
243           glVertex3f (cth2 * rb, sth2 * rb, z2);
244           glVertex3f (cth2 * ra, sth2 * ra, z2);
245           polys++;
246           glEnd();
247         }
248
249       rb = orb;
250     }
251   glEnd();
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 }