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