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