Merged progression and putt-collisions
[neverball] / share / ball.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14
15 #include <stdlib.h>
16 #include <string.h>
17
18 #include "vec3.h"
19 #include "glext.h"
20 #include "config.h"
21 #include "solid_gl.h"
22 #include "image.h"
23
24 /*---------------------------------------------------------------------------*/
25
26 #define IMG_DEFAULT "ball/default.png"
27 #define IMG_ARBBALL "ball/arbball.png"
28
29 static int has_solid = 0;
30 static int has_inner = 0;
31 static int has_outer = 0;
32
33 static struct s_file solid;
34 static struct s_file inner;
35 static struct s_file outer;
36
37 #define F_PENDULUM   1
38 #define F_DRAWBACK   2
39 #define F_DRAWCLIP   4
40 #define F_DEPTHMASK  8
41 #define F_DEPTHTEST 16
42
43 static int solid_flags;
44 static int inner_flags;
45 static int outer_flags;
46
47 static float solid_alpha;
48 static float inner_alpha;
49 static float outer_alpha;
50
51 static GLuint oldball_list;
52 static GLuint oldball_text;
53 static GLuint arbball_list;
54 static GLuint arbball_text;
55
56 /*---------------------------------------------------------------------------*/
57
58 /* These are the faces of an octahedron in positive longitude/latitude. */
59
60 static float oldball_octahedron[8][3][2] = {
61     {{   0.0f,  90.0f }, {   0.0f, 0.0f }, {  90.0f, 0.0f }},
62     {{  90.0f,  90.0f }, {  90.0f, 0.0f }, { 180.0f, 0.0f }},
63     {{ 180.0f,  90.0f }, { 180.0f, 0.0f }, { 270.0f, 0.0f }},
64     {{ 270.0f,  90.0f }, { 270.0f, 0.0f }, { 360.0f, 0.0f }},
65     {{   0.0f, -90.0f }, {  90.0f, 0.0f }, {   0.0f, 0.0f }},
66     {{  90.0f, -90.0f }, { 180.0f, 0.0f }, {  90.0f, 0.0f }},
67     {{ 180.0f, -90.0f }, { 270.0f, 0.0f }, { 180.0f, 0.0f }},
68     {{ 270.0f, -90.0f }, { 360.0f, 0.0f }, { 270.0f, 0.0f }},
69 };
70
71 static void oldball_midpoint(float *P, const float *A, const float *B)
72 {
73     float D[2];
74
75     /* The haversine midpoint method. */
76
77     D[0] = fcosf(B[1]) * fcosf(B[0] - A[0]);
78     D[1] = fcosf(B[1]) * fsinf(B[0] - A[0]);
79
80     P[0] = A[0] + fatan2f(D[1], fcosf(A[1]) + D[0]);
81
82     P[1] = fatan2f(fsinf(A[1]) +
83                    fsinf(B[1]),
84                    fsqrtf((fcosf(A[1]) + D[0]) *
85                           (fcosf(A[1]) + D[0]) + D[1] * D[1])); 
86 }
87
88 static void oldball_vertex(const float *p)
89 {
90     /* Draw a vertex with normal and texture coordinate at the given lon/lat. */
91
92     const float x = fsinf(p[0]) * fcosf(p[1]);
93     const float y =               fsinf(p[1]);
94     const float z = fcosf(p[0]) * fcosf(p[1]);
95
96     glTexCoord2f((+p[0]               ) / V_RAD(360.0f),
97                  (-p[1] + V_RAD(90.0f)) / V_RAD(180.0f));
98
99     glNormal3f(x, y, z);
100     glVertex3f(x, y, z);
101 }
102
103 static void oldball_subdiv(const float *a,
104                         const float *b,
105                         const float *c, int D)
106 {
107     if (D > 0)
108     {
109         /* Recursively subdivide the given triangle. */
110
111         float d[2];
112         float e[2];
113         float f[2];
114
115         oldball_midpoint(d, a, b);
116         oldball_midpoint(e, b, c);
117         oldball_midpoint(f, c, a);
118
119         oldball_subdiv(a, d, f, D - 1);
120         oldball_subdiv(d, b, e, D - 1);
121         oldball_subdiv(f, e, c, D - 1);
122         oldball_subdiv(d, e, f, D - 1);
123     }
124     else
125     {
126         /* Draw the given triangle. */
127
128         oldball_vertex(a);
129         oldball_vertex(b);
130         oldball_vertex(c);
131     }
132 }
133
134 void oldball_init(int b)
135 {
136     char name[MAXSTR];
137
138     strncpy(name, IMG_DEFAULT, MAXSTR - 12);
139
140     if ((oldball_text = make_image_from_file(name)))
141     {
142         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
143         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
144     }
145
146     oldball_list = glGenLists(1);
147
148     glNewList(oldball_list, GL_COMPILE);
149     {
150 #if 1
151         int i, d = b ? 4 : 3;
152
153         glBegin(GL_TRIANGLES);
154         {
155             for (i = 0; i < 8; ++i)
156             {
157                 float a[2];
158                 float b[2];
159                 float c[2];
160
161                 a[0] = V_RAD(oldball_octahedron[i][0][0]);
162                 a[1] = V_RAD(oldball_octahedron[i][0][1]);
163
164                 b[0] = V_RAD(oldball_octahedron[i][1][0]);
165                 b[1] = V_RAD(oldball_octahedron[i][1][1]);
166
167                 c[0] = V_RAD(oldball_octahedron[i][2][0]);
168                 c[1] = V_RAD(oldball_octahedron[i][2][1]);
169
170                 oldball_subdiv(a, b, c, d);
171             }
172         }
173         glEnd();
174 #else
175         int i, slices = b ? 32 : 16;
176         int j, stacks = b ? 16 :  8;
177
178         for (i = 0; i < stacks; i++)
179         {
180             float k0 = (float)  i      / stacks;
181             float k1 = (float) (i + 1) / stacks;
182
183             float s0 = fsinf(V_PI * (k0 - 0.5));
184             float c0 = fcosf(V_PI * (k0 - 0.5));
185             float s1 = fsinf(V_PI * (k1 - 0.5));
186             float c1 = fcosf(V_PI * (k1 - 0.5));
187
188             glBegin(GL_QUAD_STRIP);
189             {
190                 for (j = 0; j <= slices; j++)
191                 {
192                     float k = (float) j / slices;
193                     float s = fsinf(V_PI * k * 2.0);
194                     float c = fcosf(V_PI * k * 2.0);
195
196                     glTexCoord2f(k, k0);
197                     glNormal3f(s * c0, c * c0, s0);
198                     glVertex3f(s * c0, c * c0, s0);
199
200                     glTexCoord2f(k, k1);
201                     glNormal3f(s * c1, c * c1, s1);
202                     glVertex3f(s * c1, c * c1, s1);
203                 }
204             }
205             glEnd();
206         }
207 #endif
208     }
209     glEndList();
210
211     strncpy(name, IMG_ARBBALL, MAXSTR - 12);
212
213     if ((arbball_text = make_image_from_file(name)))
214     {
215         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
216         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
217     }
218
219     arbball_list = glGenLists(1);
220
221     glNewList(arbball_list, GL_COMPILE);
222     {
223 #if 1
224         int i, d = b ? 4 : 3;
225
226         glBegin(GL_TRIANGLES);
227         {
228             for (i = 0; i < 8; ++i)
229             {
230                 float a[2];
231                 float b[2];
232                 float c[2];
233
234                 a[0] = V_RAD(oldball_octahedron[i][0][0]);
235                 a[1] = V_RAD(oldball_octahedron[i][0][1]);
236
237                 b[0] = V_RAD(oldball_octahedron[i][1][0]);
238                 b[1] = V_RAD(oldball_octahedron[i][1][1]);
239
240                 c[0] = V_RAD(oldball_octahedron[i][2][0]);
241                 c[1] = V_RAD(oldball_octahedron[i][2][1]);
242
243                 oldball_subdiv(a, b, c, d);
244             }
245         }
246         glEnd();
247 #else
248         int i, slices = b ? 32 : 16;
249         int j, stacks = b ? 16 :  8;
250
251         for (i = 0; i < stacks; i++)
252         {
253             float k0 = (float)  i      / stacks;
254             float k1 = (float) (i + 1) / stacks;
255
256             float s0 = fsinf(V_PI * (k0 - 0.5));
257             float c0 = fcosf(V_PI * (k0 - 0.5));
258             float s1 = fsinf(V_PI * (k1 - 0.5));
259             float c1 = fcosf(V_PI * (k1 - 0.5));
260
261             glBegin(GL_QUAD_STRIP);
262             {
263                 for (j = 0; j <= slices; j++)
264                 {
265                     float k = (float) j / slices;
266                     float s = fsinf(V_PI * k * 2.0);
267                     float c = fcosf(V_PI * k * 2.0);
268
269                     glTexCoord2f(k, k0);
270                     glNormal3f(s * c0, c * c0, s0);
271                     glVertex3f(s * c0, c * c0, s0);
272
273                     glTexCoord2f(k, k1);
274                     glNormal3f(s * c1, c * c1, s1);
275                     glVertex3f(s * c1, c * c1, s1);
276                 }
277             }
278             glEnd();
279         }
280 #endif
281     }
282     glEndList();
283 }
284
285 void oldball_free(void)
286 {
287     if (glIsList(oldball_list))
288         glDeleteLists(oldball_list, 1);
289
290     if (glIsTexture(oldball_text))
291         glDeleteTextures(1, &oldball_text);
292
293     if (glIsList(arbball_list))
294         glDeleteLists(arbball_list, 1);
295
296     if (glIsTexture(arbball_text))
297         glDeleteTextures(1, &arbball_text);
298
299     oldball_list = 0;
300     oldball_text = 0;
301
302     arbball_list = 0;
303     arbball_text = 0;
304 }
305
306 void oldball_draw(int arb)
307 {
308     static const float a[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
309     static const float s[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
310     static const float e[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
311     static const float h[1] = { 20.0f };
312
313     glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   a);
314     glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  s);
315     glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  e);
316     glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, h);
317
318     glEnable(GL_COLOR_MATERIAL);
319     {
320         glBindTexture(GL_TEXTURE_2D, (arb) ? (arbball_text) : (oldball_text));
321
322         /* Render the ball back to front in case it is translucent. */
323
324         glDepthMask(GL_FALSE);
325         {
326             glCullFace(GL_FRONT);
327             glCallList((arb) ? (arbball_list) : (oldball_list));
328             glCullFace(GL_BACK);
329             glCallList(oldball_list);
330         }
331         glDepthMask(GL_TRUE);
332
333         /* Render the ball into the depth buffer. */
334
335         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
336         {
337             glCallList((arb) ? (arbball_list) : (oldball_list));
338         }
339         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
340
341         /* Ensure the ball is visible even when obscured by geometry. */
342
343         glDisable(GL_DEPTH_TEST);
344         {
345             glColor4f(1.0f, 1.0f, 1.0f, 0.1f);
346             glCallList((arb) ? (arbball_list) : (oldball_list));
347         }
348         glEnable(GL_DEPTH_TEST);
349     }
350     glDisable(GL_COLOR_MATERIAL);
351 }
352
353 /*---------------------------------------------------------------------------*/
354
355 #define SET(B, v, b) ((v) ? ((B) | (b)) : ((B) & ~(b)))
356
357 static int ball_opts(const struct s_file *fp, float *alpha)
358 {
359     int flags = F_DEPTHTEST;
360     int di;
361
362     for (di = 0; di < fp->dc; ++di)
363     {
364         char *k = fp->av + fp->dv[di].ai;
365         char *v = fp->av + fp->dv[di].aj;
366
367         if (strcmp(k, "pendulum")  == 0)
368             flags = SET(flags, atoi(v), F_PENDULUM);
369         if (strcmp(k, "drawback")  == 0)
370             flags = SET(flags, atoi(v), F_DRAWBACK);
371         if (strcmp(k, "drawclip")  == 0)
372             flags = SET(flags, atoi(v), F_DRAWCLIP);
373         if (strcmp(k, "depthmask") == 0)
374             flags = SET(flags, atoi(v), F_DEPTHMASK);
375         if (strcmp(k, "depthtest") == 0)
376             flags = SET(flags, atoi(v), F_DEPTHTEST);
377         if (strcmp(k, "alphatest") == 0)
378             sscanf(v, "%f", alpha);
379     }
380
381     return flags;
382 }
383
384 void ball_init(void)
385 {
386     int T = config_get_d(CONFIG_TEXTURES);
387
388     char ball_file[PATHMAX];
389     char solid_file[PATHMAX];
390     char inner_file[PATHMAX];
391     char outer_file[PATHMAX];
392
393     config_get_s(CONFIG_BALL, ball_file, PATHMAX / 2 - 12);
394
395     strncpy(solid_file, "ball/", PATHMAX);
396     strncpy(inner_file, "ball/", PATHMAX);
397     strncpy(outer_file, "ball/", PATHMAX);
398
399     strcat(solid_file, ball_file);
400     strcat(inner_file, ball_file);
401     strcat(outer_file, ball_file);
402
403     strcat(solid_file, "/");
404     strcat(inner_file, "/");
405     strcat(outer_file, "/");
406
407     strcat(solid_file, ball_file);
408     strcat(inner_file, ball_file);
409     strcat(outer_file, ball_file);
410
411     strcat(solid_file, "-solid.sol");
412     strcat(inner_file, "-inner.sol");
413     strcat(outer_file, "-outer.sol");
414
415     solid_flags = 0;
416     inner_flags = 0;
417     outer_flags = 0;
418
419     solid_alpha = 1.0f;
420     inner_alpha = 1.0f;
421     outer_alpha = 1.0f;
422
423     if ((has_solid = sol_load_gl(&solid, config_data(solid_file), T, 0)))
424         solid_flags = ball_opts(&solid, &solid_alpha);
425
426     if ((has_inner = sol_load_gl(&inner, config_data(inner_file), T, 0)))
427         inner_flags = ball_opts(&inner, &inner_alpha);
428
429     if ((has_outer = sol_load_gl(&outer, config_data(outer_file), T, 0)))
430         outer_flags = ball_opts(&outer, &outer_alpha);
431 }
432
433 void ball_free(void)
434 {
435     if (has_outer) sol_free_gl(&outer);
436     if (has_inner) sol_free_gl(&inner);
437     if (has_solid) sol_free_gl(&solid);
438
439     has_solid = has_inner = has_outer = 0;
440 }
441
442 /*---------------------------------------------------------------------------*/
443
444 static void ball_draw_solid(const float *ball_M,
445                             const float *ball_bill_M, float t)
446 {
447     if (has_solid)
448     {
449         const int mask = (solid_flags & F_DEPTHMASK);
450         const int test = (solid_flags & F_DEPTHTEST);
451
452         if (solid_alpha < 1.0f)
453         {
454             glEnable(GL_ALPHA_TEST);
455             glAlphaFunc(GL_GEQUAL, solid_alpha);
456         }
457
458         glPushMatrix();
459         {
460             /* Apply the ball rotation. */
461
462             glMultMatrixf(ball_M);
463
464             /* Draw the solid billboard geometry. */
465
466             if (solid.rc)
467             {
468                 if (test == 0) glDisable(GL_DEPTH_TEST);
469                 if (mask == 0) glDepthMask(GL_FALSE);
470                 glDisable(GL_LIGHTING);
471                 {
472                     sol_bill(&solid, ball_bill_M, t);
473                 }
474                 glEnable(GL_LIGHTING);
475                 if (mask == 0) glDepthMask(GL_TRUE);
476                 if (test == 0) glEnable(GL_DEPTH_TEST);
477             }
478
479             /* Draw the solid opaque and transparent geometry. */
480
481             sol_draw(&solid, mask, test);
482         }
483         glPopMatrix();
484
485         if (solid_alpha < 1.0f)
486             glDisable(GL_ALPHA_TEST);
487     }
488 }
489
490 static void ball_draw_inner(const float *pend_M,
491                             const float *bill_M,
492                             const float *pend_bill_M, float t)
493 {
494     if (has_inner)
495     {
496         const int pend = (inner_flags & F_PENDULUM);
497         const int mask = (inner_flags & F_DEPTHMASK);
498         const int test = (inner_flags & F_DEPTHTEST);
499
500         if (inner_alpha < 1.0f)
501         {
502             glEnable(GL_ALPHA_TEST);
503             glAlphaFunc(GL_GEQUAL, inner_alpha);
504         }
505
506         /* Apply the pendulum rotation. */
507
508         if (pend)
509         {
510             glPushMatrix();
511             glMultMatrixf(pend_M);
512         }
513
514         /* Draw the inner opaque and transparent geometry. */
515
516         sol_draw(&inner, mask, test);
517
518         /* Draw the inner billboard geometry. */
519
520         if (inner.rc)
521         {
522             if (test == 0) glDisable(GL_DEPTH_TEST);
523             if (mask == 0) glDepthMask(GL_FALSE);
524             glDisable(GL_LIGHTING);
525             {
526                 if (pend)
527                     sol_bill(&inner, pend_bill_M, t);
528                 else
529                     sol_bill(&inner, bill_M,      t);
530             }
531
532             glEnable(GL_LIGHTING);
533             if (mask == 0) glDepthMask(GL_TRUE);
534             if (test == 0) glEnable(GL_DEPTH_TEST);
535         }
536
537         if (pend)
538             glPopMatrix();
539
540         if (inner_alpha < 1.0f)
541             glDisable(GL_ALPHA_TEST);
542     }
543 }
544
545 static void ball_draw_outer(const float *pend_M,
546                             const float *bill_M,
547                             const float *pend_bill_M, float t)
548 {
549     if (has_outer)
550     {
551         const int pend = (outer_flags & F_PENDULUM);
552         const int mask = (outer_flags & F_DEPTHMASK);
553         const int test = (outer_flags & F_DEPTHTEST);
554
555         if (outer_alpha < 1.0f)
556         {
557             glEnable(GL_ALPHA_TEST);
558             glAlphaFunc(GL_GEQUAL, outer_alpha);
559         }
560
561        /* Apply the pendulum rotation. */
562
563         if (pend)
564         {
565             glPushMatrix();
566             glMultMatrixf(pend_M);
567         }
568
569         /* Draw the outer opaque and transparent geometry. */
570
571         sol_draw(&outer, mask, test);
572
573         /* Draw the outer billboard geometry. */
574
575         if (outer.rc)
576         {
577             if (test == 0) glDisable(GL_DEPTH_TEST);
578             if (mask == 0) glDepthMask(GL_FALSE);
579             glDisable(GL_LIGHTING);
580             {
581                 if (pend)
582                     sol_bill(&outer, pend_bill_M, t);
583                 else
584                     sol_bill(&outer, bill_M,      t);
585             }
586             glEnable(GL_LIGHTING);
587             if (mask == 0) glDepthMask(GL_TRUE);
588             if (test == 0) glEnable(GL_DEPTH_TEST);
589         }
590
591         if (pend)
592             glPopMatrix();
593
594         if (outer_alpha < 1.0f)
595             glDisable(GL_ALPHA_TEST);
596     }
597 }
598
599 /*---------------------------------------------------------------------------*/
600
601 static void ball_pass_inner(const float *ball_M,
602                             const float *pend_M,
603                             const float *bill_M,
604                             const float *ball_bill_M,
605                             const float *pend_bill_M, float t)
606 {
607     /* Sort the inner ball using clip planes. */
608
609     if      (inner_flags & F_DRAWCLIP)
610     {
611         glEnable(GL_CLIP_PLANE1);
612         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
613         glDisable(GL_CLIP_PLANE1);
614         
615         glEnable(GL_CLIP_PLANE2);
616         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
617         glDisable(GL_CLIP_PLANE2);
618     }
619
620     /* Sort the inner ball using face culling. */
621
622     else if (inner_flags & F_DRAWBACK)
623     {
624         glCullFace(GL_FRONT);
625         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
626         glCullFace(GL_BACK);
627         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
628     }
629
630     /* Draw the inner ball normally. */
631
632     else
633     {
634         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
635     }
636 }
637
638 static void ball_pass_solid(const float *ball_M,
639                             const float *pend_M,
640                             const float *bill_M,
641                             const float *ball_bill_M,
642                             const float *pend_bill_M, float t)
643 {
644     /* Sort the solid ball with the inner ball using clip planes. */
645
646     if      (solid_flags & F_DRAWCLIP)
647     {
648         glEnable(GL_CLIP_PLANE1);
649         ball_draw_solid(ball_M,                 ball_bill_M, t);
650         glDisable(GL_CLIP_PLANE1);
651         
652         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
653
654         glEnable(GL_CLIP_PLANE2);
655         ball_draw_solid(ball_M,                 ball_bill_M, t);
656         glDisable(GL_CLIP_PLANE2);
657     }
658
659     /* Sort the solid ball with the inner ball using face culling. */
660
661     else if (solid_flags & F_DRAWBACK)
662     {
663         glCullFace(GL_FRONT);
664         ball_draw_solid(ball_M,                 ball_bill_M, t);
665         glCullFace(GL_BACK);
666         
667         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
668         ball_draw_solid(ball_M,                 ball_bill_M, t);
669     }
670
671     /* Draw the solid ball after the inner ball. */
672
673     else
674     {
675         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
676         ball_draw_solid(ball_M,                 ball_bill_M, t);
677     }
678 }
679
680 static void ball_pass_outer(const float *ball_M,
681                             const float *pend_M,
682                             const float *bill_M,
683                             const float *ball_bill_M,
684                             const float *pend_bill_M, float t)
685 {
686     /* Sort the outer ball with the solid ball using clip planes. */
687
688     if      (outer_flags & F_DRAWCLIP)
689     {
690         glEnable(GL_CLIP_PLANE1);
691         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
692         glDisable(GL_CLIP_PLANE1);
693         
694         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
695
696         glEnable(GL_CLIP_PLANE2);
697         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
698         glDisable(GL_CLIP_PLANE2);
699     }
700
701     /* Sort the outer ball with the solid ball using face culling. */
702
703     else if (outer_flags & F_DRAWBACK)
704     {
705         glCullFace(GL_FRONT);
706         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
707         glCullFace(GL_BACK);
708         
709         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
710         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
711     }
712
713     /* Draw the outer ball after the solid ball. */
714
715     else
716     {
717         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
718         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
719     }
720 }
721
722 /*---------------------------------------------------------------------------*/
723
724 void ball_draw(const float *ball_M,
725                const float *pend_M,
726                const float *bill_M, float t)
727 {
728     /* Compute transforms for ball and pendulum billboards. */
729
730     float ball_T[16], ball_bill_M[16];
731     float pend_T[16], pend_bill_M[16];
732
733     m_xps(ball_T, ball_M);
734     m_xps(pend_T, pend_M);
735     m_xps(pend_T, pend_M);
736
737     m_mult(ball_bill_M, ball_T, bill_M);
738     m_mult(pend_bill_M, pend_T, bill_M);
739
740     /* Go to GREAT pains to ensure all layers are drawn back-to-front. */
741
742     ball_pass_outer(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
743 }
744
745 /*---------------------------------------------------------------------------*/