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