Experimenting with foreground billboards.
[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 #define color_cmp(a, b) ((a)[0] == (b)[0] && \
75                          (a)[1] == (b)[1] && \
76                          (a)[2] == (b)[2] && \
77                          (a)[3] == (b)[3])
78
79 static struct s_mtrl default_mtrl =
80 {
81     { 0.2f, 0.2f, 0.2f, 1.0f },
82     { 0.8f, 0.8f, 0.8f, 1.0f },
83     { 0.0f, 0.0f, 0.0f, 1.0f },
84     { 0.0f, 0.0f, 0.0f, 1.0f },
85     { 0.0f, }, 0.0f, M_OPAQUE, 0, ""
86 };
87
88 static const struct s_mtrl *sol_draw_mtrl(const struct s_file *fp,
89                                           const struct s_mtrl *mp,
90                                           const struct s_mtrl *mq)
91 {
92     /* Change material properties only as needed. */
93
94     if (!color_cmp(mp->a, mq->a))
95         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mp->a);
96     if (!color_cmp(mp->d, mq->d))
97         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   mp->d);
98     if (!color_cmp(mp->s, mq->s))
99         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  mp->s);
100     if (!color_cmp(mp->e, mq->e))
101         glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  mp->e);
102     if (mp->h[0] != mq->h[0])
103         glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mp->h);
104
105     /* Bind the texture. */
106
107     if (mp->o != mq->o)
108         glBindTexture(GL_TEXTURE_2D, mp->o);
109
110     /* Enable environment mapping. */
111
112     if ((mp->fl & M_ENVIRONMENT) && !(mq->fl & M_ENVIRONMENT))
113     {
114         glEnable(GL_TEXTURE_GEN_S);
115         glEnable(GL_TEXTURE_GEN_T);
116
117         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
118         glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
119     }
120
121     /* Disable environment mapping. */
122
123     if ((mq->fl & M_ENVIRONMENT) && !(mp->fl & M_ENVIRONMENT))
124     {
125         glDisable(GL_TEXTURE_GEN_S);
126         glDisable(GL_TEXTURE_GEN_T);
127
128         glBindTexture(GL_TEXTURE_2D, mp->o);
129     }
130
131     /* Enable additive blending. */
132
133     if ((mp->fl & M_ADDITIVE) && !(mq->fl & M_ADDITIVE))
134         glBlendFunc(GL_ONE, GL_ONE);
135
136     /* Enable standard blending. */
137
138     if ((mq->fl & M_ADDITIVE) && !(mp->fl & M_ADDITIVE))
139         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
140
141     /* Enable decal offset. */
142
143     if ((mp->fl & M_DECAL) && !(mq->fl & M_DECAL))
144     {
145         glEnable(GL_POLYGON_OFFSET_FILL);
146         glPolygonOffset(-1.0f, -2.0f);
147     }
148
149     /* Disable decal offset. */
150
151     if ((mq->fl & M_DECAL) && !(mp->fl & M_DECAL))
152         glDisable(GL_POLYGON_OFFSET_FILL);
153
154     return mp;
155 }
156
157 static const struct s_mtrl *sol_back_bill(const struct s_file *fp,
158                                           const struct s_bill *rp,
159                                           const struct s_mtrl *mp, float t)
160 {
161     float T = rp->t ? (fmodf(t, rp->t) - rp->t / 2) : 0.0f;
162
163     float w = rp->w[0] + rp->w[1] * T + rp->w[2] * T * T;
164     float h = rp->h[0] + rp->h[1] * T + rp->h[2] * T * T;
165
166     if (w > 0 && h > 0)
167     {
168         float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * T * T;
169         float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * T * T;
170         float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * T * T;
171
172         glPushMatrix();
173         {
174             float y0 = (rp->fl & B_EDGE) ? 0 : -h / 2;
175             float y1 = (rp->fl & B_EDGE) ? h : +h / 2;
176
177             glRotatef(ry, 0.0f, 1.0f, 0.0f);
178             glRotatef(rx, 1.0f, 0.0f, 0.0f);
179             glTranslatef(0.0f, 0.0f, -rp->d);
180
181             if (rp->fl & B_FLAT)
182             {
183                 glRotatef(-rx - 90.0f, 1.0f, 0.0f, 0.0f);
184                 glRotatef(-ry,         0.0f, 0.0f, 1.0f);
185             }
186             if (rp->fl & B_EDGE)
187                 glRotatef(-rx,         1.0f, 0.0f, 0.0f);
188
189             glRotatef(rz, 0.0f, 0.0f, 1.0f);
190
191             mp = sol_draw_mtrl(fp, fp->mv + rp->mi, mp);
192
193             glBegin(GL_QUADS);
194             {
195                 glTexCoord2f(0.0f, 1.0f); glVertex2f(-w / 2, y0);
196                 glTexCoord2f(1.0f, 1.0f); glVertex2f(+w / 2, y0);
197                 glTexCoord2f(1.0f, 0.0f); glVertex2f(+w / 2, y1);
198                 glTexCoord2f(0.0f, 0.0f); glVertex2f(-w / 2, y1);
199             }
200             glEnd();
201         }
202         glPopMatrix();
203     }
204
205     return mp;
206 }
207
208 static void sol_fore_bill(const struct s_file *fp, float rx, float ry)
209 {
210     const struct s_mtrl *mp = &default_mtrl;
211
212     int ri;
213
214     for (ri = 0; ri < fp->rc; ++ri)
215     {
216         const struct s_bill *rp = fp->rv + ri;
217
218         float w = (float) rp->w[0];
219         float h = (float) rp->h[0];
220
221         mp = sol_draw_mtrl(fp, fp->mv + rp->mi, mp);
222
223         glPushMatrix();
224         {
225             glTranslatef(rp->p[0], rp->p[1], rp->p[2]);
226             glRotatef(ry, 0.f, 1.f, 0.f);
227             glRotatef(rx, 1.f, 0.f, 0.f);
228
229             glBegin(GL_QUADS);
230             {
231                 glTexCoord2f(0.0f, 1.0f); glVertex2f(-w / 2, -h / 2);
232                 glTexCoord2f(1.0f, 1.0f); glVertex2f(+w / 2, -h / 2);
233                 glTexCoord2f(1.0f, 0.0f); glVertex2f(+w / 2, +h / 2);
234                 glTexCoord2f(0.0f, 0.0f); glVertex2f(-w / 2, +h / 2);
235             }
236             glEnd();
237         }
238         glPopMatrix();
239     }
240
241     mp = sol_draw_mtrl(fp, &default_mtrl, mp);
242 }
243
244 /*---------------------------------------------------------------------------*/
245
246 void sol_back(const struct s_file *fp, float n, float f, float t)
247 {
248     const struct s_mtrl *mp = &default_mtrl;
249
250     int ri;
251
252     /* Render all billboards in the given range. */
253
254     glDisable(GL_LIGHTING);
255     glDepthMask(GL_FALSE);
256     {
257         for (ri = 0; ri < fp->rc; ri++)
258             if (n <= fp->rv[ri].d && fp->rv[ri].d < f)
259                 mp = sol_back_bill(fp, fp->rv + ri, mp, t);
260
261         mp = sol_draw_mtrl(fp, &default_mtrl, mp);
262     }
263     glDepthMask(GL_TRUE);
264     glEnable(GL_LIGHTING);
265 }
266
267 /*---------------------------------------------------------------------------*/
268 /*
269  * The  following code  renders a  body in  a  ludicrously inefficient
270  * manner.  It iterates the materials and scans the data structure for
271  * geometry using each.  This  has the effect of absolutely minimizing
272  * material  changes,  texture  bindings,  and  Begin/End  pairs,  but
273  * maximizing trips through the data.
274  *
275  * However, this  is only done once  for each level.   The results are
276  * stored in display lists.  Thus, it is well worth it.
277  */
278
279 static void sol_draw_geom(const struct s_file *fp,
280                           const struct s_geom *gp, int mi)
281 {
282     if (gp->mi == mi)
283     {
284         const float *ui = fp->tv[gp->ti].u;
285         const float *uj = fp->tv[gp->tj].u;
286         const float *uk = fp->tv[gp->tk].u;
287
288         const float *ni = fp->sv[gp->si].n;
289         const float *nj = fp->sv[gp->sj].n;
290         const float *nk = fp->sv[gp->sk].n;
291
292         const float *vi = fp->vv[gp->vi].p;
293         const float *vj = fp->vv[gp->vj].p;
294         const float *vk = fp->vv[gp->vk].p;
295
296         glTexCoord2fv(ui);
297         glNormal3fv(ni);
298         glVertex3fv(vi);
299
300         glTexCoord2fv(uj);
301         glNormal3fv(nj);
302         glVertex3fv(vj);
303
304         glTexCoord2fv(uk);
305         glNormal3fv(nk);
306         glVertex3fv(vk);
307     }
308 }
309
310 static void sol_draw_lump(const struct s_file *fp,
311                           const struct s_lump *lp, int mi)
312 {
313     int i;
314
315     for (i = 0; i < lp->gc; i++)
316         sol_draw_geom(fp, fp->gv + fp->iv[lp->g0 + i], mi);
317 }
318
319 static const struct s_mtrl *sol_draw_body(const struct s_file *fp,
320                                           const struct s_body *bp,
321                                           const struct s_mtrl *mp,
322                                           int fl, int decal)
323 {
324     int mi, li, gi;
325
326     /* Iterate all materials of the correct opacity. */
327
328     for (mi = 0; mi < fp->mc; mi++)
329         if ((fp->mv[mi].fl & fl) && (fp->mv[mi].fl & M_DECAL) == decal)
330         {
331             if (sol_enum_mtrl(fp, bp, mi))
332             {
333                 /* Set the material state. */
334
335                 mp = sol_draw_mtrl(fp, fp->mv + mi, mp);
336
337                 /* Render all geometry of that material. */
338
339                 glBegin(GL_TRIANGLES);
340                 {
341                     for (li = 0; li < bp->lc; li++)
342                         sol_draw_lump(fp, fp->lv + bp->l0 + li, mi);
343                     for (gi = 0; gi < bp->gc; gi++)
344                         sol_draw_geom(fp, fp->gv + fp->iv[bp->g0 + gi], mi);
345                 }
346                 glEnd();
347             }
348         }
349
350     return mp;
351 }
352
353 static void sol_draw_list(const struct s_file *fp,
354                           const struct s_body *bp, GLuint list)
355 {
356     float p[3];
357
358     sol_body_p(p, fp, bp);
359
360     glPushMatrix();
361     {
362         /* Translate a moving body. */
363
364         glTranslatef(p[0], p[1], p[2]);
365
366         /* Draw the body. */
367
368         glCallList(list);
369     }
370     glPopMatrix();
371 }
372
373 void sol_draw(const struct s_file *fp, float rx, float ry)
374 {
375     int bi;
376
377     /* Render all opaque geometry into the color and depth buffers. */
378
379     for (bi = 0; bi < fp->bc; bi++)
380         if (fp->bv[bi].ol)
381             sol_draw_list(fp, fp->bv + bi, fp->bv[bi].ol);
382
383     /* Render all translucent geometry into only the color buffer. */
384
385     glDepthMask(GL_FALSE);
386     {
387         for (bi = 0; bi < fp->bc; bi++)
388             if (fp->bv[bi].tl)
389                 sol_draw_list(fp, fp->bv + bi, fp->bv[bi].tl);
390
391         
392         /* Render all foreground billboards unlit. */
393
394         glDisable(GL_LIGHTING);
395         {
396             sol_fore_bill(fp, rx, ry);
397         }
398         glEnable(GL_LIGHTING);
399     }
400     glDepthMask(GL_TRUE);
401
402 }
403
404 void sol_refl(const struct s_file *fp)
405 {
406     int bi;
407
408     /* Render all reflective geometry into the color and depth buffers. */
409
410     for (bi = 0; bi < fp->bc; bi++)
411         if (fp->bv[bi].rl)
412             sol_draw_list(fp, fp->bv + bi, fp->bv[bi].rl);
413 }
414
415 /*---------------------------------------------------------------------------*/
416
417 static void sol_shad_geom(const struct s_file *fp,
418                           const struct s_geom *gp, int mi)
419 {
420     if (gp->mi == mi)
421     {
422         const float *vi = fp->vv[gp->vi].p;
423         const float *vj = fp->vv[gp->vj].p;
424         const float *vk = fp->vv[gp->vk].p;
425
426         glTexCoord2f(vi[0], vi[2]);
427         glVertex3fv(vi);
428
429         glTexCoord2f(vj[0], vj[2]);
430         glVertex3fv(vj);
431
432         glTexCoord2f(vk[0], vk[2]);
433         glVertex3fv(vk);
434     }
435 }
436
437 static void sol_shad_lump(const struct s_file *fp,
438                           const struct s_lump *lp, int mi)
439 {
440     int i;
441
442     for (i = 0; i < lp->gc; i++)
443         sol_shad_geom(fp, fp->gv + fp->iv[lp->g0 + i], mi);
444 }
445
446 static void sol_shad_body(const struct s_file *fp,
447                           const struct s_body *bp,
448                           int fl, int decal)
449 {
450     int mi, li, gi;
451
452     if (fl & M_DECAL)
453     {
454         glEnable(GL_POLYGON_OFFSET_FILL);
455         glPolygonOffset(-1.0f, -2.0f);
456     }
457
458     glBegin(GL_TRIANGLES);
459     {
460         for (mi = 0; mi < fp->mc; mi++)
461             if ((fp->mv[mi].fl & fl) && (fp->mv[mi].fl & M_DECAL) == decal)
462             {
463                 for (li = 0; li < bp->lc; li++)
464                     sol_shad_lump(fp, fp->lv + bp->l0 + li, mi);
465                 for (gi = 0; gi < bp->gc; gi++)
466                     sol_shad_geom(fp, fp->gv + fp->iv[bp->g0 + gi], mi);
467             }
468     }
469     glEnd();
470
471     if (fl & M_DECAL)
472         glDisable(GL_POLYGON_OFFSET_FILL);
473 }
474
475 static void sol_shad_list(const struct s_file *fp,
476                           const struct s_body *bp, GLuint list)
477 {
478     float p[3];
479
480     sol_body_p(p, fp, bp);
481
482     glPushMatrix();
483     {
484         /* Translate a moving body. */
485
486         glTranslatef(p[0], p[1], p[2]);
487
488         /* Translate the shadow on a moving body. */
489
490         glMatrixMode(GL_TEXTURE);
491         {
492             glPushMatrix();
493             glTranslatef(p[0], p[2], 0.0f);
494         }
495         glMatrixMode(GL_MODELVIEW);
496
497         /* Draw the body. */
498
499         glCallList(list);
500
501         /* Pop the shadow translation. */
502
503         glMatrixMode(GL_TEXTURE);
504         {
505             glPopMatrix();
506         }
507         glMatrixMode(GL_MODELVIEW);
508     }
509     glPopMatrix();
510 }
511
512 void sol_shad(const struct s_file *fp)
513 {
514     int bi;
515
516     /* Render all shadowed geometry. */
517
518     glDepthMask(GL_FALSE);
519     {
520         for (bi = 0; bi < fp->bc; bi++)
521             if (fp->bv[bi].sl)
522                 sol_shad_list(fp, fp->bv + bi, fp->bv[bi].sl);
523     }
524     glDepthMask(GL_TRUE);
525 }
526
527 /*---------------------------------------------------------------------------*/
528
529 static void sol_load_objects(struct s_file *fp, int s)
530 {
531     int i;
532
533     /* Here we sort geometry into display lists by material type. */
534
535     for (i = 0; i < fp->bc; i++)
536     {
537         struct s_body *bp = fp->bv + i;
538
539         int on = sol_enum_body(fp, bp, M_OPAQUE);
540         int tn = sol_enum_body(fp, bp, M_TRANSPARENT);
541         int rn = sol_enum_body(fp, bp, M_REFLECTIVE);
542         int dn = sol_enum_body(fp, bp, M_DECAL);
543
544         /* Draw all opaque geometry, decals last. */
545
546         if (on)
547         {
548             fp->bv[i].ol = glGenLists(1);
549
550             glNewList(fp->bv[i].ol, GL_COMPILE);
551             {
552                 const struct s_mtrl *mp = &default_mtrl;
553
554                 mp = sol_draw_body(fp, fp->bv + i, mp, M_OPAQUE, 0);
555                 mp = sol_draw_body(fp, fp->bv + i, mp, M_OPAQUE, M_DECAL);
556                 mp = sol_draw_mtrl(fp, &default_mtrl, mp);
557             }
558             glEndList();
559         }
560         else fp->bv[i].ol = 0;
561
562         /* Draw all translucent geometry, decals first. */
563
564         if (tn)
565         {
566             fp->bv[i].tl = glGenLists(1);
567
568             glNewList(fp->bv[i].tl, GL_COMPILE);
569             {
570                 const struct s_mtrl *mp = &default_mtrl;
571
572                 mp = sol_draw_body(fp, fp->bv + i, mp, M_TRANSPARENT, M_DECAL);
573                 mp = sol_draw_body(fp, fp->bv + i, mp, M_TRANSPARENT, 0);
574                 mp = sol_draw_mtrl(fp, &default_mtrl, mp);
575             }
576             glEndList();
577         }
578         else fp->bv[i].tl = 0;
579
580         /* Draw all reflective geometry. */
581
582         if (rn)
583         {
584             fp->bv[i].rl = glGenLists(1);
585
586             glNewList(fp->bv[i].rl, GL_COMPILE);
587             {
588                 const struct s_mtrl *mp = &default_mtrl;
589
590                 mp = sol_draw_body(fp, fp->bv + i, mp, M_REFLECTIVE, 0);
591                 mp = sol_draw_mtrl(fp, &default_mtrl, mp);
592             }
593             glEndList();
594         }
595         else fp->bv[i].rl = 0;
596
597         /* Draw all shadowed geometry. */
598
599         if (s && (on || rn))
600         {
601             fp->bv[i].sl = glGenLists(1);
602
603             glNewList(fp->bv[i].sl, GL_COMPILE);
604             {
605                 if (on) sol_shad_body(fp, fp->bv + i, M_OPAQUE, 0);
606                 if (rn) sol_shad_body(fp, fp->bv + i, M_REFLECTIVE, 0);
607                 if (dn) sol_shad_body(fp, fp->bv + i, M_OPAQUE, M_DECAL);
608             }
609             glEndList();
610         }
611         else fp->bv[i].sl = 0;
612     }
613 }
614
615 static GLuint sol_find_texture(const char *name)
616 {
617     char png[MAXSTR];
618     char jpg[MAXSTR];
619
620     GLuint o;
621
622     /* Prefer a lossless copy of the texture over a lossy compression. */
623
624     strncpy(png, name, PATHMAX); strcat(png, ".png");
625     strncpy(jpg, name, PATHMAX); strcat(jpg, ".jpg");
626
627     /* Check for a PNG. */
628
629     if ((o = make_image_from_file(png)))
630         return o;
631
632     /* Check for a JPG. */
633
634     if ((o = make_image_from_file(jpg)))
635         return o;
636
637     return 0;
638 }
639
640 static void sol_load_textures(struct s_file *fp, int k)
641 {
642     int i;
643
644     /* Load the image referenced by each material. */
645
646     for (i = 0; i < fp->mc; i++)
647         if ((fp->mv[i].o = sol_find_texture(fp->mv[i].f)))
648         {
649             /* Set the texture to clamp or repeat based on material type. */
650
651             if (fp->mv[i].fl & M_CLAMPED)
652             {
653                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
654                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
655             }
656             else
657             {
658                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
659                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
660             }
661         }
662 }
663
664 /*---------------------------------------------------------------------------*/
665
666 int sol_load_gl(struct s_file *fp, const char *filename, int k, int s)
667 {
668     if (sol_load_only_file(fp, filename))
669     {
670         sol_load_textures(fp, k);
671         sol_load_objects (fp, s);
672         return 1;
673     }
674     return 0;
675 }
676
677 /*---------------------------------------------------------------------------*/
678
679 void sol_free_gl(struct s_file *fp)
680 {
681     int i;
682
683     for (i = 0; i < fp->mc; i++)
684     {
685         if (glIsTexture(fp->mv[i].o))
686             glDeleteTextures(1, &fp->mv[i].o);
687     }
688
689     for (i = 0; i < fp->bc; i++)
690     {
691         if (glIsList(fp->bv[i].ol))
692             glDeleteLists(fp->bv[i].ol, 1);
693         if (glIsList(fp->bv[i].tl))
694             glDeleteLists(fp->bv[i].tl, 1);
695         if (glIsList(fp->bv[i].rl))
696             glDeleteLists(fp->bv[i].rl, 1);
697     }
698
699     sol_free(fp);
700 }
701
702 /*---------------------------------------------------------------------------*/