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