Split SOL data structures into base, varying and rendering parts
[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(const float *ball_M,
123                             const float *ball_bill_M, float t)
124 {
125     if (has_solid)
126     {
127         const int mask = (solid_flags & F_DEPTHMASK);
128         const int test = (solid_flags & F_DEPTHTEST);
129
130         if (solid_alpha < 1.0f)
131         {
132             glEnable(GL_ALPHA_TEST);
133             glAlphaFunc(GL_GEQUAL, solid_alpha);
134         }
135
136         glPushMatrix();
137         {
138             /* Apply the ball rotation. */
139
140             glMultMatrixf(ball_M);
141
142             /* Draw the solid billboard geometry. */
143
144             if (solid.base.rc)
145             {
146                 if (test == 0) glDisable(GL_DEPTH_TEST);
147                 if (mask == 0) glDepthMask(GL_FALSE);
148                 glDisable(GL_LIGHTING);
149                 {
150                     sol_bill(&solid.draw, ball_bill_M, t);
151                 }
152                 glEnable(GL_LIGHTING);
153                 if (mask == 0) glDepthMask(GL_TRUE);
154                 if (test == 0) glEnable(GL_DEPTH_TEST);
155             }
156
157             /* Draw the solid opaque and transparent geometry. */
158
159             sol_draw(&solid.draw, mask, test);
160         }
161         glPopMatrix();
162
163         if (solid_alpha < 1.0f)
164             glDisable(GL_ALPHA_TEST);
165     }
166 }
167
168 static void ball_draw_inner(const float *pend_M,
169                             const float *bill_M,
170                             const float *pend_bill_M, float t)
171 {
172     if (has_inner)
173     {
174         const int pend = (inner_flags & F_PENDULUM);
175         const int mask = (inner_flags & F_DEPTHMASK);
176         const int test = (inner_flags & F_DEPTHTEST);
177
178         if (inner_alpha < 1.0f)
179         {
180             glEnable(GL_ALPHA_TEST);
181             glAlphaFunc(GL_GEQUAL, inner_alpha);
182         }
183
184         /* Apply the pendulum rotation. */
185
186         if (pend)
187         {
188             glPushMatrix();
189             glMultMatrixf(pend_M);
190         }
191
192         /* Draw the inner opaque and transparent geometry. */
193
194         sol_draw(&inner.draw, mask, test);
195
196         /* Draw the inner billboard geometry. */
197
198         if (inner.base.rc)
199         {
200             if (test == 0) glDisable(GL_DEPTH_TEST);
201             if (mask == 0) glDepthMask(GL_FALSE);
202             glDisable(GL_LIGHTING);
203             {
204                 if (pend)
205                     sol_bill(&inner.draw, pend_bill_M, t);
206                 else
207                     sol_bill(&inner.draw, bill_M,      t);
208             }
209
210             glEnable(GL_LIGHTING);
211             if (mask == 0) glDepthMask(GL_TRUE);
212             if (test == 0) glEnable(GL_DEPTH_TEST);
213         }
214
215         if (pend)
216             glPopMatrix();
217
218         if (inner_alpha < 1.0f)
219             glDisable(GL_ALPHA_TEST);
220     }
221 }
222
223 static void ball_draw_outer(const float *pend_M,
224                             const float *bill_M,
225                             const float *pend_bill_M, float t)
226 {
227     if (has_outer)
228     {
229         const int pend = (outer_flags & F_PENDULUM);
230         const int mask = (outer_flags & F_DEPTHMASK);
231         const int test = (outer_flags & F_DEPTHTEST);
232
233         if (outer_alpha < 1.0f)
234         {
235             glEnable(GL_ALPHA_TEST);
236             glAlphaFunc(GL_GEQUAL, outer_alpha);
237         }
238
239        /* Apply the pendulum rotation. */
240
241         if (pend)
242         {
243             glPushMatrix();
244             glMultMatrixf(pend_M);
245         }
246
247         /* Draw the outer opaque and transparent geometry. */
248
249         sol_draw(&outer.draw, mask, test);
250
251         /* Draw the outer billboard geometry. */
252
253         if (outer.base.rc)
254         {
255             if (test == 0) glDisable(GL_DEPTH_TEST);
256             if (mask == 0) glDepthMask(GL_FALSE);
257             glDisable(GL_LIGHTING);
258             {
259                 if (pend)
260                     sol_bill(&outer.draw, pend_bill_M, t);
261                 else
262                     sol_bill(&outer.draw, bill_M,      t);
263             }
264             glEnable(GL_LIGHTING);
265             if (mask == 0) glDepthMask(GL_TRUE);
266             if (test == 0) glEnable(GL_DEPTH_TEST);
267         }
268
269         if (pend)
270             glPopMatrix();
271
272         if (outer_alpha < 1.0f)
273             glDisable(GL_ALPHA_TEST);
274     }
275 }
276
277 /*---------------------------------------------------------------------------*/
278
279 static void ball_pass_inner(const float *ball_M,
280                             const float *pend_M,
281                             const float *bill_M,
282                             const float *ball_bill_M,
283                             const float *pend_bill_M, float t)
284 {
285     /* Sort the inner ball using clip planes. */
286
287     if      (inner_flags & F_DRAWCLIP)
288     {
289         glEnable(GL_CLIP_PLANE1);
290         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
291         glDisable(GL_CLIP_PLANE1);
292
293         glEnable(GL_CLIP_PLANE2);
294         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
295         glDisable(GL_CLIP_PLANE2);
296     }
297
298     /* Sort the inner ball using face culling. */
299
300     else if (inner_flags & F_DRAWBACK)
301     {
302         glCullFace(GL_FRONT);
303         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
304         glCullFace(GL_BACK);
305         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
306     }
307
308     /* Draw the inner ball normally. */
309
310     else
311     {
312         ball_draw_inner(        pend_M, bill_M,              pend_bill_M, t);
313     }
314 }
315
316 static void ball_pass_solid(const float *ball_M,
317                             const float *pend_M,
318                             const float *bill_M,
319                             const float *ball_bill_M,
320                             const float *pend_bill_M, float t)
321 {
322     /* Sort the solid ball with the inner ball using clip planes. */
323
324     if      (solid_flags & F_DRAWCLIP)
325     {
326         glEnable(GL_CLIP_PLANE1);
327         ball_draw_solid(ball_M,                 ball_bill_M, t);
328         glDisable(GL_CLIP_PLANE1);
329
330         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
331
332         glEnable(GL_CLIP_PLANE2);
333         ball_draw_solid(ball_M,                 ball_bill_M, t);
334         glDisable(GL_CLIP_PLANE2);
335     }
336
337     /* Sort the solid ball with the inner ball using face culling. */
338
339     else if (solid_flags & F_DRAWBACK)
340     {
341         glCullFace(GL_FRONT);
342         ball_draw_solid(ball_M,                 ball_bill_M, t);
343         glCullFace(GL_BACK);
344
345         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
346         ball_draw_solid(ball_M,                 ball_bill_M, t);
347     }
348
349     /* Draw the solid ball after the inner ball. */
350
351     else
352     {
353         ball_pass_inner(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
354         ball_draw_solid(ball_M,                 ball_bill_M, t);
355     }
356 }
357
358 static void ball_pass_outer(const float *ball_M,
359                             const float *pend_M,
360                             const float *bill_M,
361                             const float *ball_bill_M,
362                             const float *pend_bill_M, float t)
363 {
364     /* Sort the outer ball with the solid ball using clip planes. */
365
366     if      (outer_flags & F_DRAWCLIP)
367     {
368         glEnable(GL_CLIP_PLANE1);
369         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
370         glDisable(GL_CLIP_PLANE1);
371
372         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
373
374         glEnable(GL_CLIP_PLANE2);
375         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
376         glDisable(GL_CLIP_PLANE2);
377     }
378
379     /* Sort the outer ball with the solid ball using face culling. */
380
381     else if (outer_flags & F_DRAWBACK)
382     {
383         glCullFace(GL_FRONT);
384         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
385         glCullFace(GL_BACK);
386
387         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
388         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
389     }
390
391     /* Draw the outer ball after the solid ball. */
392
393     else
394     {
395         ball_pass_solid(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
396         ball_draw_outer(        pend_M, bill_M,              pend_bill_M, t);
397     }
398 }
399
400 /*---------------------------------------------------------------------------*/
401
402 void ball_draw(const float *ball_M,
403                const float *pend_M,
404                const float *bill_M, float t)
405 {
406     /* Compute transforms for ball and pendulum billboards. */
407
408     float ball_T[16], ball_bill_M[16];
409     float pend_T[16], pend_bill_M[16];
410
411     m_xps(ball_T, ball_M);
412     m_xps(pend_T, pend_M);
413
414     m_mult(ball_bill_M, ball_T, bill_M);
415     m_mult(pend_bill_M, pend_T, bill_M);
416
417     /* Go to GREAT pains to ensure all layers are drawn back-to-front. */
418
419     ball_pass_outer(ball_M, pend_M, bill_M, ball_bill_M, pend_bill_M, t);
420 }
421
422 /*---------------------------------------------------------------------------*/