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