Fix redundant glTexEnv calls
[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 "common.h"
22
23 #include "solid_draw.h"
24
25 /*---------------------------------------------------------------------------*/
26
27 static int has_solid = 0;
28 static int has_inner = 0;
29 static int has_outer = 0;
30
31 static struct s_full solid;
32 static struct s_full inner;
33 static struct s_full outer;
34
35 #define F_PENDULUM   1
36 #define F_DRAWBACK   2
37 #define F_DRAWCLIP   4
38 #define F_DEPTHMASK  8
39 #define F_DEPTHTEST 16
40
41 static int solid_flags;
42 static int inner_flags;
43 static int outer_flags;
44
45 static float solid_alpha;
46 static float inner_alpha;
47 static float outer_alpha;
48
49 /*---------------------------------------------------------------------------*/
50
51 #define SET(B, v, b) ((v) ? ((B) | (b)) : ((B) & ~(b)))
52
53 static int ball_opts(const struct s_base *base, float *alpha)
54 {
55     int flags = F_DEPTHTEST;
56     int di;
57
58     for (di = 0; di < base->dc; ++di)
59     {
60         char *k = base->av + base->dv[di].ai;
61         char *v = base->av + base->dv[di].aj;
62
63         if (strcmp(k, "pendulum")  == 0)
64             flags = SET(flags, atoi(v), F_PENDULUM);
65         if (strcmp(k, "drawback")  == 0)
66             flags = SET(flags, atoi(v), F_DRAWBACK);
67         if (strcmp(k, "drawclip")  == 0)
68             flags = SET(flags, atoi(v), F_DRAWCLIP);
69         if (strcmp(k, "depthmask") == 0)
70             flags = SET(flags, atoi(v), F_DEPTHMASK);
71         if (strcmp(k, "depthtest") == 0)
72             flags = SET(flags, atoi(v), F_DEPTHTEST);
73         if (strcmp(k, "alphatest") == 0)
74             sscanf(v, "%f", alpha);
75     }
76
77     return flags;
78 }
79
80 void ball_init(void)
81 {
82     char *solid_file = concat_string(config_get_s(CONFIG_BALL_FILE),
83                                      "-solid.sol", NULL);
84     char *inner_file = concat_string(config_get_s(CONFIG_BALL_FILE),
85                                      "-inner.sol", NULL);
86     char *outer_file = concat_string(config_get_s(CONFIG_BALL_FILE),
87                                      "-outer.sol", NULL);
88
89     solid_flags = 0;
90     inner_flags = 0;
91     outer_flags = 0;
92
93     solid_alpha = 1.0f;
94     inner_alpha = 1.0f;
95     outer_alpha = 1.0f;
96
97     if ((has_solid = sol_load_full(&solid, solid_file, 0)))
98         solid_flags = ball_opts(&solid.base, &solid_alpha);
99
100     if ((has_inner = sol_load_full(&inner, inner_file, 0)))
101         inner_flags = ball_opts(&inner.base, &inner_alpha);
102
103     if ((has_outer = sol_load_full(&outer, outer_file, 0)))
104         outer_flags = ball_opts(&outer.base, &outer_alpha);
105
106     free(solid_file);
107     free(inner_file);
108     free(outer_file);
109 }
110
111 void ball_free(void)
112 {
113     if (has_outer) sol_free_full(&outer);
114     if (has_inner) sol_free_full(&inner);
115     if (has_solid) sol_free_full(&solid);
116
117     has_solid = has_inner = has_outer = 0;
118 }
119
120 /*---------------------------------------------------------------------------*/
121
122 static void ball_draw_solid(struct s_rend *rend,
123                             const float *ball_M,
124                             const float *ball_bill_M, float t)
125 {
126     if (has_solid)
127     {
128         const int mask = (solid_flags & F_DEPTHMASK);
129         const int test = (solid_flags & F_DEPTHTEST);
130
131         if (solid_alpha < 1.0f)
132         {
133             glEnable(GL_ALPHA_TEST);
134             glAlphaFunc(GL_GEQUAL, solid_alpha);
135         }
136
137         glPushMatrix();
138         {
139             /* Apply the ball rotation. */
140
141             glMultMatrixf(ball_M);
142
143             /* Draw the solid billboard geometry. */
144
145             if (solid.base.rc)
146             {
147                 if (test == 0) glDisable(GL_DEPTH_TEST);
148                 if (mask == 0) glDepthMask(GL_FALSE);
149                 glDisable(GL_LIGHTING);
150                 {
151                     sol_bill(&solid.draw, rend, ball_bill_M, t);
152                 }
153                 glEnable(GL_LIGHTING);
154                 if (mask == 0) glDepthMask(GL_TRUE);
155                 if (test == 0) glEnable(GL_DEPTH_TEST);
156             }
157
158             /* Draw the solid opaque and transparent geometry. */
159
160             sol_draw(&solid.draw, rend, mask, test);
161         }
162         glPopMatrix();
163
164         if (solid_alpha < 1.0f)
165             glDisable(GL_ALPHA_TEST);
166     }
167 }
168
169 static void ball_draw_inner(struct s_rend *rend,
170                             const float *pend_M,
171                             const float *bill_M,
172                             const float *pend_bill_M, float t)
173 {
174     if (has_inner)
175     {
176         const int pend = (inner_flags & F_PENDULUM);
177         const int mask = (inner_flags & F_DEPTHMASK);
178         const int test = (inner_flags & F_DEPTHTEST);
179
180         if (inner_alpha < 1.0f)
181         {
182             glEnable(GL_ALPHA_TEST);
183             glAlphaFunc(GL_GEQUAL, inner_alpha);
184         }
185
186         /* Apply the pendulum rotation. */
187
188         if (pend)
189         {
190             glPushMatrix();
191             glMultMatrixf(pend_M);
192         }
193
194         /* Draw the inner opaque and transparent geometry. */
195
196         sol_draw(&inner.draw, rend, mask, test);
197
198         /* Draw the inner billboard geometry. */
199
200         if (inner.base.rc)
201         {
202             if (test == 0) glDisable(GL_DEPTH_TEST);
203             if (mask == 0) glDepthMask(GL_FALSE);
204             glDisable(GL_LIGHTING);
205             {
206                 if (pend)
207                     sol_bill(&inner.draw, rend, pend_bill_M, t);
208                 else
209                     sol_bill(&inner.draw, rend, bill_M,      t);
210             }
211
212             glEnable(GL_LIGHTING);
213             if (mask == 0) glDepthMask(GL_TRUE);
214             if (test == 0) glEnable(GL_DEPTH_TEST);
215         }
216
217         if (pend)
218             glPopMatrix();
219
220         if (inner_alpha < 1.0f)
221             glDisable(GL_ALPHA_TEST);
222     }
223 }
224
225 static void ball_draw_outer(struct s_rend *rend,
226                             const float *pend_M,
227                             const float *bill_M,
228                             const float *pend_bill_M, float t)
229 {
230     if (has_outer)
231     {
232         const int pend = (outer_flags & F_PENDULUM);
233         const int mask = (outer_flags & F_DEPTHMASK);
234         const int test = (outer_flags & F_DEPTHTEST);
235
236         if (outer_alpha < 1.0f)
237         {
238             glEnable(GL_ALPHA_TEST);
239             glAlphaFunc(GL_GEQUAL, outer_alpha);
240         }
241
242         /* Apply the pendulum rotation. */
243
244         if (pend)
245         {
246             glPushMatrix();
247             glMultMatrixf(pend_M);
248         }
249
250         /* Draw the outer opaque and transparent geometry. */
251
252         sol_draw(&outer.draw, rend, mask, test);
253
254         /* Draw the outer billboard geometry. */
255
256         if (outer.base.rc)
257         {
258             if (test == 0) glDisable(GL_DEPTH_TEST);
259             if (mask == 0) glDepthMask(GL_FALSE);
260             glDisable(GL_LIGHTING);
261             {
262                 if (pend)
263                     sol_bill(&outer.draw, rend, pend_bill_M, t);
264                 else
265                     sol_bill(&outer.draw, rend, bill_M,      t);
266             }
267             glEnable(GL_LIGHTING);
268             if (mask == 0) glDepthMask(GL_TRUE);
269             if (test == 0) glEnable(GL_DEPTH_TEST);
270         }
271
272         if (pend)
273             glPopMatrix();
274
275         if (outer_alpha < 1.0f)
276             glDisable(GL_ALPHA_TEST);
277     }
278 }
279
280 /*---------------------------------------------------------------------------*/
281
282 static void ball_pass_inner(struct s_rend *rend,
283                             const float *ball_M,
284                             const float *pend_M,
285                             const float *bill_M,
286                             const float *ball_bill_M,
287                             const float *pend_bill_M, float t)
288 {
289     /* Sort the inner ball using clip planes. */
290
291     if      (inner_flags & F_DRAWCLIP)
292     {
293         glEnable(GL_CLIP_PLANE1);
294         ball_draw_inner(rend, pend_M, bill_M, pend_bill_M, t);
295         glDisable(GL_CLIP_PLANE1);
296
297         glEnable(GL_CLIP_PLANE2);
298         ball_draw_inner(rend, pend_M, bill_M, pend_bill_M, t);
299         glDisable(GL_CLIP_PLANE2);
300     }
301
302     /* Sort the inner ball using face culling. */
303
304     else if (inner_flags & F_DRAWBACK)
305     {
306         glCullFace(GL_FRONT);
307         ball_draw_inner(rend, pend_M, bill_M, pend_bill_M, t);
308         glCullFace(GL_BACK);
309         ball_draw_inner(rend, pend_M, bill_M, pend_bill_M, t);
310     }
311
312     /* Draw the inner ball normally. */
313
314     else
315     {
316         ball_draw_inner(rend, pend_M, bill_M, pend_bill_M, t);
317     }
318 }
319
320 static void ball_pass_solid(struct s_rend *rend,
321                             const float *ball_M,
322                             const float *pend_M,
323                             const float *bill_M,
324                             const float *ball_bill_M,
325                             const float *pend_bill_M, float t)
326 {
327     /* Sort the solid ball with the inner ball using clip planes. */
328
329     if      (solid_flags & F_DRAWCLIP)
330     {
331         glEnable(GL_CLIP_PLANE1);
332         ball_draw_solid(rend, ball_M,                 ball_bill_M, t);
333         glDisable(GL_CLIP_PLANE1);
334
335         ball_pass_inner(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
336
337         glEnable(GL_CLIP_PLANE2);
338         ball_draw_solid(rend, ball_M,                 ball_bill_M, t);
339         glDisable(GL_CLIP_PLANE2);
340     }
341
342     /* Sort the solid ball with the inner ball using face culling. */
343
344     else if (solid_flags & F_DRAWBACK)
345     {
346         glCullFace(GL_FRONT);
347         ball_draw_solid(rend, ball_M,                 ball_bill_M, t);
348         glCullFace(GL_BACK);
349
350         ball_pass_inner(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
351         ball_draw_solid(rend, ball_M,                 ball_bill_M, t);
352     }
353
354     /* Draw the solid ball after the inner ball. */
355
356     else
357     {
358         ball_pass_inner(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
359         ball_draw_solid(rend, ball_M,                 ball_bill_M, t);
360     }
361 }
362
363 static void ball_pass_outer(struct s_rend *rend,
364                             const float *ball_M,
365                             const float *pend_M,
366                             const float *bill_M,
367                             const float *ball_bill_M,
368                             const float *pend_bill_M, float t)
369 {
370     /* Sort the outer ball with the solid ball using clip planes. */
371
372     if      (outer_flags & F_DRAWCLIP)
373     {
374         glEnable(GL_CLIP_PLANE1);
375         ball_draw_outer(rend,         pend_M, bill_M,              pend_bill_M, t);
376         glDisable(GL_CLIP_PLANE1);
377
378         ball_pass_solid(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
379
380         glEnable(GL_CLIP_PLANE2);
381         ball_draw_outer(rend,         pend_M, bill_M,              pend_bill_M, t);
382         glDisable(GL_CLIP_PLANE2);
383     }
384
385     /* Sort the outer ball with the solid ball using face culling. */
386
387     else if (outer_flags & F_DRAWBACK)
388     {
389         glCullFace(GL_FRONT);
390         ball_draw_outer(rend,         pend_M, bill_M,              pend_bill_M, t);
391         glCullFace(GL_BACK);
392
393         ball_pass_solid(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
394         ball_draw_outer(rend,         pend_M, bill_M,              pend_bill_M, t);
395     }
396
397     /* Draw the outer ball after the solid ball. */
398
399     else
400     {
401         ball_pass_solid(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
402         ball_draw_outer(rend,         pend_M, bill_M,              pend_bill_M, t);
403     }
404 }
405
406 /*---------------------------------------------------------------------------*/
407
408 void ball_draw(struct s_rend *rend,
409                const float *ball_M,
410                const float *pend_M,
411                const float *bill_M, float t)
412 {
413     /* Compute transforms for ball and pendulum billboards. */
414
415     float ball_T[16], ball_bill_M[16];
416     float pend_T[16], pend_bill_M[16];
417
418     m_xps(ball_T, ball_M);
419     m_xps(pend_T, pend_M);
420
421     m_mult(ball_bill_M, ball_T, bill_M);
422     m_mult(pend_bill_M, pend_T, bill_M);
423
424     /* Go to GREAT pains to ensure all layers are drawn back-to-front. */
425
426     ball_pass_outer(rend, ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
427 }
428
429 /*---------------------------------------------------------------------------*/