Shared code clean-up. One more and I suppose it's done.
[neverball] / share / solid_gl.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 <SDL.h>
16 #include <SDL_rwops.h>
17 #include <SDL_image.h>
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <math.h>
23
24 #include "glext.h"
25 #include "vec3.h"
26 #include "base_image.h"
27 #include "solid_gl.h"
28 #include "base_config.h"
29
30 /*---------------------------------------------------------------------------*/
31
32 static int sol_enum_mtrl(const struct s_file *fp,
33                          const struct s_body *bp, int mi)
34 {
35     int li, gi, c = 0;
36
37     /* Count all lump geoms with this material. */
38
39     for (li = 0; li < bp->lc; li++)
40     {
41         int g0 = fp->lv[bp->l0 + li].g0;
42         int gc = fp->lv[bp->l0 + li].gc;
43
44         for (gi = 0; gi < gc; gi++)
45             if (fp->gv[fp->iv[g0 + gi]].mi == mi)
46                 c++;
47     }
48                     
49     /* Count all body geoms with this material. */
50
51     for (gi = 0; gi < bp->gc; gi++)
52         if (fp->gv[fp->iv[bp->g0 + gi]].mi == mi)
53             c++;
54
55     return c;
56 }
57
58 static int sol_enum_body(const struct s_file *fp,
59                            const struct s_body *bp, int fl)
60 {
61     int mi, c = 0;
62
63     /* Count all geoms with this flag. */
64
65     for (mi = 0; mi < fp->mc; mi++)
66         if (fp->mv[mi].fl & fl)
67             c = c + sol_enum_mtrl(fp, bp, mi);
68
69     return c;
70 }
71
72 /*---------------------------------------------------------------------------*/
73
74 static void sol_draw_mtrl(const struct s_file *fp, int i)
75 {
76     const struct s_mtrl *mp = fp->mv + i;
77
78     glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mp->a);
79     glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   mp->d);
80     glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  mp->s);
81     glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  mp->e);
82     glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mp->h);
83
84     if (mp->fl & M_ENVIRONMENT)
85     {
86         glEnable(GL_TEXTURE_GEN_S);
87         glEnable(GL_TEXTURE_GEN_T);
88
89         glBindTexture(GL_TEXTURE_2D, mp->o);
90
91         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
92         glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
93     }
94     else
95     {
96         glDisable(GL_TEXTURE_GEN_S);
97         glDisable(GL_TEXTURE_GEN_T);
98
99         glBindTexture(GL_TEXTURE_2D, mp->o);
100     }
101
102     if (mp->fl & M_ADDITIVE)
103         glBlendFunc(GL_ONE, GL_ONE);
104     else
105         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
106
107 }
108
109 static void sol_draw_bill(const struct s_file *fp,
110                           const struct s_bill *rp, float t)
111 {
112     float T  = fmodf(t, rp->t) - rp->t / 2;
113
114     float w  = rp->w[0] + rp->w[1] * T + rp->w[2] * T * T;
115     float h  = rp->h[0] + rp->h[1] * T + rp->h[2] * T * T;
116
117     if (w > 0 && h > 0)
118     {
119         float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * T * T;
120         float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * T * T;
121         float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * T * T;
122
123         glPushMatrix();
124         {
125             float y0 = (rp->fl & B_EDGE) ? 0 : -h / 2;
126             float y1 = (rp->fl & B_EDGE) ? h : +h / 2;
127
128             glRotatef(ry, 0.0f, 1.0f, 0.0f);
129             glRotatef(rx, 1.0f, 0.0f, 0.0f);
130             glTranslatef(0.0f, 0.0f, -rp->d);
131
132             if (rp->fl & B_FLAT)
133             {
134                 glRotatef(-rx - 90.0f, 1.0f, 0.0f, 0.0f);
135                 glRotatef(-ry,         0.0f, 0.0f, 1.0f);
136             }
137             if (rp->fl & B_EDGE)
138                 glRotatef(-rx,         1.0f, 0.0f, 0.0f);
139
140             glRotatef(rz, 0.0f, 0.0f, 1.0f);
141
142             sol_draw_mtrl(fp, rp->mi);
143
144             glBegin(GL_QUADS);
145             {
146                 glTexCoord2f(0.0f, 1.0f); glVertex2f(-w / 2, y0);
147                 glTexCoord2f(1.0f, 1.0f); glVertex2f(+w / 2, y0);
148                 glTexCoord2f(1.0f, 0.0f); glVertex2f(+w / 2, y1);
149                 glTexCoord2f(0.0f, 0.0f); glVertex2f(-w / 2, y1);
150             }
151             glEnd();
152         }
153         glPopMatrix();
154     }
155 }
156
157 void sol_back(const struct s_file *fp, float n, float f, float t)
158 {
159     int ri;
160
161     glPushAttrib(GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT);
162     {
163         /* Render all billboards in the given range. */
164
165         glDisable(GL_LIGHTING);
166         glDepthMask(GL_FALSE);
167
168         for (ri = 0; ri < fp->rc; ri++)
169             if (n <= fp->rv[ri].d && fp->rv[ri].d < f)
170                 sol_draw_bill(fp, fp->rv + ri, t);
171     }
172     glPopAttrib();
173 }
174
175 /*---------------------------------------------------------------------------*/
176 /*
177  * The  following code  renders a  body in  a  ludicrously inefficient
178  * manner.  It iterates the materials and scans the data structure for
179  * geometry using each.  This  has the effect of absolutely minimizing
180  * material  changes,  texture  bindings,  and  Begin/End  pairs,  but
181  * maximizing trips through the data.
182  *
183  * However, this  is only done once  for each level.   The results are
184  * stored in display lists.  Thus, it is well worth it.
185  */
186
187 static void sol_draw_geom(const struct s_file *fp,
188                           const struct s_geom *gp, int mi)
189 {
190     if (gp->mi == mi)
191     {
192         const float *ui = fp->tv[gp->ti].u;
193         const float *uj = fp->tv[gp->tj].u;
194         const float *uk = fp->tv[gp->tk].u;
195
196         const float *ni = fp->sv[gp->si].n;
197         const float *nj = fp->sv[gp->sj].n;
198         const float *nk = fp->sv[gp->sk].n;
199
200         const float *vi = fp->vv[gp->vi].p;
201         const float *vj = fp->vv[gp->vj].p;
202         const float *vk = fp->vv[gp->vk].p;
203
204         glTexCoord2fv(ui);
205         glNormal3fv(ni);
206         glVertex3fv(vi);
207
208         glTexCoord2fv(uj);
209         glNormal3fv(nj);
210         glVertex3fv(vj);
211
212         glTexCoord2fv(uk);
213         glNormal3fv(nk);
214         glVertex3fv(vk);
215     }
216 }
217
218 static void sol_draw_lump(const struct s_file *fp,
219                           const struct s_lump *lp, int mi)
220 {
221     int i;
222
223     for (i = 0; i < lp->gc; i++)
224         sol_draw_geom(fp, fp->gv + fp->iv[lp->g0 + i], mi);
225 }
226
227 static void sol_draw_body(const struct s_file *fp,
228                           const struct s_body *bp, int fl)
229 {
230     int mi, li, gi;
231
232     /* Iterate all materials of the correct opacity. */
233
234     for (mi = 0; mi < fp->mc; mi++)
235         if (fp->mv[mi].fl & fl)
236         {
237             if (sol_enum_mtrl(fp, bp, mi))
238             {
239                 /* Set the material state. */
240
241                 sol_draw_mtrl(fp, mi);
242
243                 /* Render all geometry of that material. */
244
245                 glBegin(GL_TRIANGLES);
246                 {
247                     for (li = 0; li < bp->lc; li++)
248                         sol_draw_lump(fp, fp->lv + bp->l0 + li, mi);
249                     for (gi = 0; gi < bp->gc; gi++)
250                         sol_draw_geom(fp, fp->gv + fp->iv[bp->g0 + gi], mi);
251                 }
252                 glEnd();
253             }
254         }
255 }
256
257 static void sol_draw_list(const struct s_file *fp,
258                           const struct s_body *bp, GLuint list)
259 {
260     float p[3];
261
262     sol_body_p(p, fp, bp);
263
264     glPushMatrix();
265     {
266         /* Translate a moving body. */
267
268         glTranslatef(p[0], p[1], p[2]);
269
270         /* Draw the body. */
271
272         glCallList(list);
273     }
274     glPopMatrix();
275 }
276
277 void sol_draw(const struct s_file *fp)
278 {
279     int bi;
280
281     glPushAttrib(GL_TEXTURE_BIT      |
282                  GL_LIGHTING_BIT     |
283                  GL_COLOR_BUFFER_BIT |
284                  GL_DEPTH_BUFFER_BIT);
285     {
286         /* Render all obaque geometry into the color and depth buffers. */
287
288         for (bi = 0; bi < fp->bc; bi++)
289             if (fp->bv[bi].ol)
290                 sol_draw_list(fp, fp->bv + bi, fp->bv[bi].ol);
291
292         /* Render all translucent geometry into only the color buffer. */
293
294         glDepthMask(GL_FALSE);
295
296         glEnable(GL_BLEND);
297         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
298
299         for (bi = 0; bi < fp->bc; bi++)
300             if (fp->bv[bi].tl)
301                 sol_draw_list(fp, fp->bv + bi, fp->bv[bi].tl);
302     }
303     glPopAttrib();
304 }
305
306 void sol_refl(const struct s_file *fp)
307 {
308     int bi;
309
310     glPushAttrib(GL_LIGHTING_BIT);
311     {
312         /* Render all reflective geometry into the color and depth buffers. */
313
314         glEnable(GL_BLEND);
315         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
316
317         for (bi = 0; bi < fp->bc; bi++)
318             if (fp->bv[bi].rl)
319                 sol_draw_list(fp, fp->bv + bi, fp->bv[bi].rl);
320     }
321     glPopAttrib();
322 }
323
324 /*---------------------------------------------------------------------------*/
325
326 static void sol_shad_geom(const struct s_file *fp,
327                           const struct s_geom *gp, int mi)
328 {
329     if (gp->mi == mi)
330     {
331         const float *vi = fp->vv[gp->vi].p;
332         const float *vj = fp->vv[gp->vj].p;
333         const float *vk = fp->vv[gp->vk].p;
334
335         glTexCoord2f(vi[0], vi[2]);
336         glVertex3fv(vi);
337
338         glTexCoord2f(vj[0], vj[2]);
339         glVertex3fv(vj);
340
341         glTexCoord2f(vk[0], vk[2]);
342         glVertex3fv(vk);
343     }
344 }
345
346 static void sol_shad_lump(const struct s_file *fp,
347                           const struct s_lump *lp, int mi)
348 {
349     int i;
350
351     for (i = 0; i < lp->gc; i++)
352         sol_shad_geom(fp, fp->gv + fp->iv[lp->g0 + i], mi);
353 }
354
355 static void sol_shad_body(const struct s_file *fp,
356                           const struct s_body *bp, int fl)
357 {
358     int mi, li, gi;
359
360     glBegin(GL_TRIANGLES);
361     {
362         for (mi = 0; mi < fp->mc; mi++)
363             if (fp->mv[mi].fl & fl)
364             {
365                 for (li = 0; li < bp->lc; li++)
366                     sol_shad_lump(fp, fp->lv + bp->l0 + li, mi);
367                 for (gi = 0; gi < bp->gc; gi++)
368                     sol_shad_geom(fp, fp->gv + fp->iv[bp->g0 + gi], mi);
369             }
370     }
371     glEnd();
372 }
373
374 static void sol_shad_list(const struct s_file *fp,
375                           const struct s_body *bp, GLuint list)
376 {
377     float p[3];
378
379     sol_body_p(p, fp, bp);
380
381     glPushMatrix();
382     {
383         /* Translate a moving body. */
384
385         glTranslatef(p[0], p[1], p[2]);
386
387         /* Translate the shadow on a moving body. */
388
389         glMatrixMode(GL_TEXTURE);
390         {
391             glPushMatrix();
392             glTranslatef(p[0], p[2], 0.0f);
393         }
394         glMatrixMode(GL_MODELVIEW);
395         
396         /* Draw the body. */
397
398         glCallList(list);
399
400         /* Pop the shadow translation. */
401
402         glMatrixMode(GL_TEXTURE);
403         {
404             glPopMatrix();
405         }
406         glMatrixMode(GL_MODELVIEW);
407     }
408     glPopMatrix();
409 }
410
411 void sol_shad(const struct s_file *fp)
412 {
413     int bi;
414
415     glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT);
416     {
417         /* Render all shadowed geometry. */
418
419         glEnable(GL_BLEND);
420         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
421
422         glDepthFunc(GL_LEQUAL);
423         glDepthMask(GL_FALSE);
424
425         for (bi = 0; bi < fp->bc; bi++)
426             if (fp->bv[bi].sl)
427                 sol_shad_list(fp, fp->bv + bi, fp->bv[bi].sl);
428     }
429     glPopAttrib();
430 }
431
432 /*---------------------------------------------------------------------------*/
433
434 static void sol_load_objects(struct s_file *fp, int s)
435 {
436     int i;
437
438     for (i = 0; i < fp->bc; i++)
439     {
440         struct s_body *bp = fp->bv + i;
441
442         /* Draw all opaque geometry. */
443
444         if (sol_enum_body(fp, bp, M_OPAQUE | M_ENVIRONMENT))
445         {
446             fp->bv[i].ol = glGenLists(1);
447             
448             glNewList(fp->bv[i].ol, GL_COMPILE);
449             {
450                 sol_draw_body(fp, fp->bv + i, M_OPAQUE | M_ENVIRONMENT);
451             }
452             glEndList();
453         }
454         else fp->bv[i].ol = 0;
455
456         /* Draw all translucent geometry. */
457
458         if (sol_enum_body(fp, bp, M_TRANSPARENT))
459         {
460             fp->bv[i].tl = glGenLists(1);
461
462             glNewList(fp->bv[i].tl, GL_COMPILE);
463             {
464                 sol_draw_body(fp, fp->bv + i, M_TRANSPARENT);
465             }
466             glEndList();
467         }
468         else fp->bv[i].tl = 0;
469
470         /* Draw all reflective geometry. */
471
472         if (sol_enum_body(fp, bp, M_REFLECTIVE))
473         {
474             fp->bv[i].rl = glGenLists(1);
475
476             glNewList(fp->bv[i].rl, GL_COMPILE);
477             {
478                 sol_draw_body(fp, fp->bv + i, M_REFLECTIVE);
479             }
480             glEndList();
481         }
482         else fp->bv[i].rl = 0;
483
484         /* Draw all shadowed geometry. */
485
486         if (s && sol_enum_body(fp, bp, M_SHADOWED))
487         {
488             fp->bv[i].sl = glGenLists(1);
489
490             glNewList(fp->bv[i].sl, GL_COMPILE);
491             {
492                 sol_shad_body(fp, fp->bv + i, M_SHADOWED);
493             }
494             glEndList();
495         }
496         else fp->bv[i].sl = 0;
497     }
498 }
499
500 static SDL_Surface *sol_find_texture(const char *name)
501 {
502     char png[MAXSTR];
503     char tga[MAXSTR];
504     char jpg[MAXSTR];
505     SDL_Surface *s;
506
507     /* Prefer a lossless copy of the texture over a lossy compression. */
508
509     strncpy(png, name, PATHMAX); strcat(png, ".png");
510     strncpy(tga, name, PATHMAX); strcat(tga, ".tga");
511     strncpy(jpg, name, PATHMAX); strcat(jpg, ".jpg");
512
513     /* Check for a PNG. */
514
515     if ((s = IMG_Load(config_data(png))))
516         return s;
517
518     /* Check for a TGA, swapping channels if found. */
519
520     if ((s = IMG_Load(config_data(tga))))
521     {
522         image_swab(s);
523         return s;
524     }
525
526     /* Check for a JPG. */
527
528     if ((s = IMG_Load(config_data(jpg))))
529         return s;
530
531     return NULL;
532 }
533
534 static void sol_load_textures(struct s_file *fp, int k)
535 {
536     SDL_Surface *s;
537     SDL_Surface *d;
538
539     int i;
540
541     for (i = 0; i < fp->mc; i++)
542         if ((s = sol_find_texture(fp->mv[i].f)))
543         {
544             GLenum f = (s->format->BitsPerPixel == 32) ? GL_RGBA : GL_RGB;
545
546             glGenTextures(1, &fp->mv[i].o);
547             glBindTexture(GL_TEXTURE_2D, fp->mv[i].o);
548
549             if (k > 1)
550             {
551                 /* Create a new buffer and copy the scaled image to it. */
552
553                 if ((d = image_scale(s, k)))
554                 {
555                     glTexImage2D(GL_TEXTURE_2D, 0, f, d->w, d->h, 0, f,
556                                  GL_UNSIGNED_BYTE, d->pixels);
557                     SDL_FreeSurface(d);
558                 }
559             }
560             else
561                 glTexImage2D(GL_TEXTURE_2D, 0, f, s->w, s->h, 0, f,
562                              GL_UNSIGNED_BYTE, s->pixels);
563
564             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
565             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
566
567             /* Set the texture to clamp or repeat based on material type. */
568
569             if (fp->mv[i].fl & M_CLAMPED)
570             {
571                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
572                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
573             }
574             else
575             {
576                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
577                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
578             }
579
580             SDL_FreeSurface(s);
581         }
582 }
583
584 /*---------------------------------------------------------------------------*/
585
586 int sol_load_gl(struct s_file *fp, const char *filename, int k, int s)
587 {
588     if (sol_load_only_file(fp, filename))
589     {
590         sol_load_textures(fp, k);
591         sol_load_objects (fp, s);
592         return 1;
593     }
594     else
595         return 0;
596 }
597
598 /*---------------------------------------------------------------------------*/
599
600 void sol_free_gl(struct s_file *fp)
601 {
602     int i;
603
604     for (i = 0; i < fp->mc; i++)
605     {
606         if (glIsTexture(fp->mv[i].o))
607             glDeleteTextures(1, &fp->mv[i].o);
608     }
609
610     for (i = 0; i < fp->bc; i++)
611     {
612         if (glIsList(fp->bv[i].ol))
613             glDeleteLists(fp->bv[i].ol, 1);
614         if (glIsList(fp->bv[i].tl))
615             glDeleteLists(fp->bv[i].tl, 1);
616         if (glIsList(fp->bv[i].rl))
617             glDeleteLists(fp->bv[i].rl, 1);
618     }
619
620     sol_free(fp);
621 }
622
623 /*---------------------------------------------------------------------------*/