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