Merge branch 'gles'
[neverball] / share / solid_draw.c
index 302134e..1ab4289 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "glext.h"
 #include "vec3.h"
+#include "geom.h"
 #include "image.h"
 #include "base_image.h"
 #include "base_config.h"
 
 /*---------------------------------------------------------------------------*/
 
-static int sol_enum_mtrl(const struct s_base *base,
-                         const struct b_body *bp, int mi)
+/* EXCLUDED material flags for each rendering pass. */
+
+static const int pass_ex[] = {
+    M_REFLECTIVE | M_TRANSPARENT | M_DECAL,
+    M_REFLECTIVE | M_TRANSPARENT,
+    M_REFLECTIVE,
+    M_REFLECTIVE | M_DECAL,
+    0,
+};
+
+/* INCLUDED material flags for each rendering pass. */
+
+static const int pass_in[] = {
+    0,
+    M_DECAL,
+    M_DECAL | M_TRANSPARENT,
+    M_TRANSPARENT,
+    M_REFLECTIVE,
+};
+
+/*---------------------------------------------------------------------------*/
+
+static void sol_transform(const struct s_vary *vary,
+                          const struct v_body *bp, int ui)
 {
-    int li, gi, c = 0;
+    float a;
+    float e[4];
+    float p[3];
+    float v[3];
 
-    /* Count all lump geoms with this material. */
+    /* Apply the body position and rotation to the model-view matrix. */
 
-    for (li = 0; li < bp->lc; li++)
+    sol_body_p(p, vary, bp->pi, bp->t);
+    sol_body_e(e, vary, bp, 0);
+
+    q_as_axisangle(e, v, &a);
+
+    glTranslatef(p[0], p[1], p[2]);
+    glRotatef(V_DEG(a), v[0], v[1], v[2]);
+
+    /* Apply the shadow transform to the texture matrix. */
+
+    if (ui >= 0 && ui < vary->uc && vary->uv[ui].r > 0.0f)
     {
-        int g0 = base->lv[bp->l0 + li].g0;
-        int gc = base->lv[bp->l0 + li].gc;
+        struct v_ball *up = vary->uv + ui;
+
+        if (tex_env_stage(TEX_STAGE_SHADOW))
+        {
+            glMatrixMode(GL_TEXTURE);
+            {
+                float k = 0.25f / up->r;
+
+                glLoadIdentity();
+
+                /* Center the shadow texture on the ball. */
+
+                glTranslatef(0.5f, 0.5f, 0.0f);
+
+                /* Transform ball XZ position to ST texture coordinate. */
+
+                glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
 
-        for (gi = 0; gi < gc; gi++)
-            if (base->gv[base->iv[g0 + gi]].mi == mi)
-                c++;
+                /* Scale the shadow texture to the radius of the ball. */
+
+                glScalef(k, k, k);
+
+                /* Move the shadow texture under the ball. */
+
+                glTranslatef(-up->p[0], -up->p[1], -up->p[2]);
+
+                /* Apply the body position and rotation. */
+
+                glTranslatef(p[0], p[1], p[2]);
+                glRotatef(V_DEG(a), v[0], v[1], v[2]);
+
+                /* Vertically center clipper texture on ball position. */
+
+                if (tex_env_stage(TEX_STAGE_CLIP))
+                {
+                    glLoadIdentity();
+                    glTranslatef(p[0] - up->p[0],
+                                 p[1] - up->p[1] + 0.5f,
+                                 p[2] - up->p[2]);
+                    glRotatef(V_DEG(a), v[0], v[1], v[2]);
+
+                }
+            }
+            glMatrixMode(GL_MODELVIEW);
+
+            tex_env_stage(TEX_STAGE_TEXTURE);
+        }
     }
+}
 
-    /* Count all body geoms with this material. */
+/*---------------------------------------------------------------------------*/
 
-    for (gi = 0; gi < bp->gc; gi++)
-        if (base->gv[base->iv[bp->g0 + gi]].mi == mi)
-            c++;
+static void sol_load_bill(struct s_draw *draw)
+{
+    static const GLfloat data[] = {
+        0.0f,  0.0f, -1.0f, -1.0f,
+        1.0f,  0.0f,  1.0f, -1.0f,
+        0.0f,  1.0f, -1.0f,  1.0f,
+        1.0f,  1.0f,  1.0f,  1.0f,
+
+        0.0f,  0.0f, -0.5f,  0.0f,
+        1.0f,  0.0f,  0.5f,  0.0f,
+        0.0f,  1.0f, -0.5f,  1.0f,
+        1.0f,  1.0f,  0.5f,  1.0f,
+
+        0.0f,  0.0f, -0.5f, -0.5f,
+        1.0f,  0.0f,  0.5f, -0.5f,
+        0.0f,  1.0f, -0.5f,  0.5f,
+        1.0f,  1.0f,  0.5f,  0.5f,
+    };
+
+    /* Initialize a vertex buffer object for billboard drawing. */
+
+    glGenBuffers_(1,              &draw->bill);
+    glBindBuffer_(GL_ARRAY_BUFFER, draw->bill);
+    glBufferData_(GL_ARRAY_BUFFER, sizeof (data), data, GL_STATIC_DRAW);
+    glBindBuffer_(GL_ARRAY_BUFFER, 0);
+}
 
-    return c;
+static void sol_free_bill(struct s_draw *draw)
+{
+    if (glIsBuffer_(draw->bill))
+        glDeleteBuffers_(1, &draw->bill);
 }
 
-static int sol_enum_body(const struct s_base *base,
-                         const struct b_body *bp, int fl)
+static void sol_draw_bill(GLfloat w, GLfloat h, GLboolean edge)
 {
-    int mi, c = 0;
+    glPushMatrix();
+    {
+        glScalef(0.5f * w, 0.5f * h, 1.0f);
 
-    /* Count all geoms with this flag. */
+        if (edge)
+            glTranslatef(0.0f, 0.5f, 0.0f);
 
-    for (mi = 0; mi < base->mc; mi++)
-        if (base->mv[mi].fl & fl)
-            c = c + sol_enum_mtrl(base, bp, mi);
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    }
+    glPopMatrix();
+}
 
-    return c;
+/*---------------------------------------------------------------------------*/
+
+/* NOTE: The state management here presumes that billboard rendering is      */
+/* NESTED within a wider SOL rendering process. That is: sol_draw_enable     */
+/* has been called and sol_draw_disable will be called in the future.        */
+/* Thus the "default" VBO state retained by billboard rendering is the       */
+/* state appropriate for normal SOL rendering.                               */
+
+static void sol_bill_enable(const struct s_draw *draw)
+{
+    const size_t s = sizeof (GLfloat);
+
+    glBindBuffer_(GL_ARRAY_BUFFER, draw->bill);
+
+    glTexCoordPointer(2, GL_FLOAT, s * 4, (GLvoid *) (    0));
+    glVertexPointer  (2, GL_FLOAT, s * 4, (GLvoid *) (s * 2));
+}
+
+static void sol_bill_disable(void)
+{
 }
 
 /*---------------------------------------------------------------------------*/
 
 #define tobyte(f) ((GLubyte) (f * 255.0f))
 
-#define color_cmp(a, b) (tobyte((a)[0]) == tobyte((b)[0]) && \
-                         tobyte((a)[1]) == tobyte((b)[1]) && \
-                         tobyte((a)[2]) == tobyte((b)[2]) && \
-                         tobyte((a)[3]) == tobyte((b)[3]))
-
 static struct b_mtrl default_base_mtrl =
 {
     { 0.8f, 0.8f, 0.8f, 1.0f },
     { 0.2f, 0.2f, 0.2f, 1.0f },
     { 0.0f, 0.0f, 0.0f, 1.0f },
     { 0.0f, 0.0f, 0.0f, 1.0f },
-    { 0.0f }, 0.0f, M_OPAQUE, ""
+    { 0.0f }, 0.0f, 0, ""
 };
 
+/* Nasty. */
+
 static struct d_mtrl default_draw_mtrl =
 {
-    &default_base_mtrl, 0
+    &default_base_mtrl,
+    0xffcccccc,
+    0xff333333,
+    0xff000000,
+    0xff000000,
+    0x00000000,
+    0
 };
 
-static const struct d_mtrl *sol_draw_mtrl(const struct s_draw *draw,
-                                          const struct d_mtrl *mp_draw,
-                                          const struct d_mtrl *mq_draw)
+void sol_apply_mtrl(const struct d_mtrl *mp_draw, struct s_rend *rend)
 {
     const struct b_mtrl *mp_base = mp_draw->base;
+    const struct d_mtrl *mq_draw = rend->mp;
     const struct b_mtrl *mq_base = mq_draw->base;
 
-    /* Change material properties only as needed. */
+    /* Bind the texture. */
 
-    if (!color_cmp(mp_base->a, mq_base->a))
-        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mp_base->a);
-    if (!color_cmp(mp_base->d, mq_base->d))
+    if (mp_draw->o != mq_draw->o)
+        glBindTexture(GL_TEXTURE_2D, mp_draw->o);
+
+    /* Set material properties. */
+
+    if (mp_draw->d != mq_draw->d)
         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   mp_base->d);
-    if (!color_cmp(mp_base->s, mq_base->s))
+    if (mp_draw->a != mq_draw->a)
+        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   mp_base->a);
+    if (mp_draw->s != mq_draw->s)
         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  mp_base->s);
-    if (!color_cmp(mp_base->e, mq_base->e))
+    if (mp_draw->e != mq_draw->e)
         glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  mp_base->e);
-    if (tobyte(mp_base->h[0]) != tobyte(mq_base->h[0]))
+    if (mp_draw->h != mq_draw->h)
         glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mp_base->h);
 
-    /* Bind the texture. */
+    /* Ball shadow. */
 
-    if (mp_draw->o != mq_draw->o)
-        glBindTexture(GL_TEXTURE_2D, mp_draw->o);
+    if ((mp_base->fl & M_SHADOWED) ^ (mq_base->fl & M_SHADOWED))
+    {
+        if (mp_base->fl & M_SHADOWED)
+            shad_draw_set();
+        else
+            shad_draw_clr();
+    }
 
-    /* Enable environment mapping. */
+    /* Environment mapping. */
 
-    if ((mp_base->fl & M_ENVIRONMENT) && !(mq_base->fl & M_ENVIRONMENT))
+#if !ENABLE_OPENGLES
+    if ((mp_base->fl & M_ENVIRONMENT) ^ (mq_base->fl & M_ENVIRONMENT))
     {
-        glEnable(GL_TEXTURE_GEN_S);
-        glEnable(GL_TEXTURE_GEN_T);
+        if (mp_base->fl & M_ENVIRONMENT)
+        {
+            glEnable(GL_TEXTURE_GEN_S);
+            glEnable(GL_TEXTURE_GEN_T);
 
-        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
-        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+        }
+        else
+        {
+            glDisable(GL_TEXTURE_GEN_S);
+            glDisable(GL_TEXTURE_GEN_T);
+        }
     }
+#endif
 
-    /* Disable environment mapping. */
+    /* Additive blending. */
 
-    if ((mq_base->fl & M_ENVIRONMENT) && !(mp_base->fl & M_ENVIRONMENT))
+    if ((mp_base->fl & M_ADDITIVE) ^ (mq_base->fl & M_ADDITIVE))
     {
-        glDisable(GL_TEXTURE_GEN_S);
-        glDisable(GL_TEXTURE_GEN_T);
+        if (mp_base->fl & M_ADDITIVE)
+            glBlendFunc(GL_ONE, GL_ONE);
+        else
+            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     }
 
-    /* Enable additive blending. */
-
-    if ((mp_base->fl & M_ADDITIVE) && !(mq_base->fl & M_ADDITIVE))
-        glBlendFunc(GL_ONE, GL_ONE);
-
-    /* Enable standard blending. */
-
-    if ((mq_base->fl & M_ADDITIVE) && !(mp_base->fl & M_ADDITIVE))
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-    /* Enable visibility-from-behind. */
+    /* Visibility-from-behind. */
 
-    if ((mp_base->fl & M_TWO_SIDED) && !(mq_base->fl & M_TWO_SIDED))
+    if ((mp_base->fl & M_TWO_SIDED) ^ (mq_base->fl & M_TWO_SIDED))
     {
-        glDisable(GL_CULL_FACE);
-        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
+        if (mp_base->fl & M_TWO_SIDED)
+        {
+            glDisable(GL_CULL_FACE);
+            glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1);
+        }
+        else
+        {
+            glEnable(GL_CULL_FACE);
+            glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0);
+        }
     }
 
-    /* Disable visibility-from-behind. */
+    /* Decal offset. */
 
-    if ((mq_base->fl & M_TWO_SIDED) && !(mp_base->fl & M_TWO_SIDED))
+    if ((mp_base->fl & M_DECAL) ^ (mq_base->fl & M_DECAL))
     {
-        glEnable(GL_CULL_FACE);
-        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
+        if (mp_base->fl & M_DECAL)
+        {
+            glEnable(GL_POLYGON_OFFSET_FILL);
+            glPolygonOffset(-1.0f, -2.0f);
+        }
+        else
+            glDisable(GL_POLYGON_OFFSET_FILL);
     }
 
-    /* Enable decal offset. */
+    rend->mp = mp_draw;
+}
 
-    if ((mp_base->fl & M_DECAL) && !(mq_base->fl & M_DECAL))
-    {
-        glEnable(GL_POLYGON_OFFSET_FILL);
-        glPolygonOffset(-1.0f, -2.0f);
-    }
+static GLuint sol_find_texture(const char *name)
+{
+    char png[MAXSTR];
+    char jpg[MAXSTR];
 
-    /* Disable decal offset. */
+    GLuint o;
 
-    if ((mq_base->fl & M_DECAL) && !(mp_base->fl & M_DECAL))
-        glDisable(GL_POLYGON_OFFSET_FILL);
+    /* Prefer a lossless copy of the texture over a lossy compression. */
 
-    return mp_draw;
-}
+    strncpy(png, name, PATHMAX); strcat(png, ".png");
+    strncpy(jpg, name, PATHMAX); strcat(jpg, ".jpg");
 
-static const struct d_mtrl *sol_back_bill(const struct s_draw *draw,
-                                           const struct b_bill *rp,
-                                           const struct d_mtrl *mp,
-                                           float t)
-{
-    float T = (rp->t > 0.0f) ? (fmodf(t, rp->t) - rp->t / 2) : 0.0f;
+    /* Check for a PNG. */
 
-    float w = rp->w[0] + rp->w[1] * T + rp->w[2] * T * T;
-    float h = rp->h[0] + rp->h[1] * T + rp->h[2] * T * T;
+    if ((o = make_image_from_file(png)))
+        return o;
 
-    if (w > 0 && h > 0)
-    {
-        float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * T * T;
-        float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * T * T;
-        float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * T * T;
+    /* Check for a JPG. */
 
-        glPushMatrix();
-        {
-            float y0 = (rp->fl & B_EDGE) ? 0 : -h / 2;
-            float y1 = (rp->fl & B_EDGE) ? h : +h / 2;
+    if ((o = make_image_from_file(jpg)))
+        return o;
 
-            glRotatef(ry, 0.0f, 1.0f, 0.0f);
-            glRotatef(rx, 1.0f, 0.0f, 0.0f);
-            glTranslatef(0.0f, 0.0f, -rp->d);
+    return 0;
+}
 
-            if (rp->fl & B_FLAT)
-            {
-                glRotatef(-rx - 90.0f, 1.0f, 0.0f, 0.0f);
-                glRotatef(-ry,         0.0f, 0.0f, 1.0f);
-            }
-            if (rp->fl & B_EDGE)
-                glRotatef(-rx,         1.0f, 0.0f, 0.0f);
+void sol_load_mtrl(struct d_mtrl *mp, const struct b_mtrl *mq)
+{
+    mp->base = mq;
 
-            glRotatef(rz, 0.0f, 0.0f, 1.0f);
+    if ((mp->o = sol_find_texture(_(mq->f))))
+    {
+        /* Set the texture to clamp or repeat based on material type. */
 
-            mp = sol_draw_mtrl(draw, draw->mv + rp->mi, mp);
+        if (mq->fl & M_CLAMP_S)
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        else
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 
-            glBegin(GL_QUADS);
-            {
-                glTexCoord2f(0.0f, 0.0f); glVertex2f(-w / 2, y0);
-                glTexCoord2f(1.0f, 0.0f); glVertex2f(+w / 2, y0);
-                glTexCoord2f(1.0f, 1.0f); glVertex2f(+w / 2, y1);
-                glTexCoord2f(0.0f, 1.0f); glVertex2f(-w / 2, y1);
-            }
-            glEnd();
-        }
-        glPopMatrix();
+        if (mq->fl & M_CLAMP_T)
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        else
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
     }
 
-    return mp;
+    /* Cache the 32-bit material values for quick comparison. */
+
+    mp->d = (tobyte(mq->d[0]))
+        |   (tobyte(mq->d[1]) <<  8)
+        |   (tobyte(mq->d[2]) << 16)
+        |   (tobyte(mq->d[3]) << 24);
+    mp->a = (tobyte(mq->a[0]))
+        |   (tobyte(mq->a[1]) <<  8)
+        |   (tobyte(mq->a[2]) << 16)
+        |   (tobyte(mq->a[3]) << 24);
+    mp->s = (tobyte(mq->s[0]))
+        |   (tobyte(mq->s[1]) <<  8)
+        |   (tobyte(mq->s[2]) << 16)
+        |   (tobyte(mq->s[3]) << 24);
+    mp->e = (tobyte(mq->e[0]))
+        |   (tobyte(mq->e[1]) <<  8)
+        |   (tobyte(mq->e[2]) << 16)
+        |   (tobyte(mq->e[3]) << 24);
+    mp->h = (tobyte(mq->h[0]));
 }
 
-/*---------------------------------------------------------------------------*/
+void sol_free_mtrl(struct d_mtrl *mp)
+{
+    if (glIsTexture(mp->o))
+        glDeleteTextures(1, &mp->o);
+}
 
-void sol_back(const struct s_draw *draw, float n, float f, float t)
+static int sol_test_mtrl(const struct d_mtrl *mp, int p)
 {
-    const struct d_mtrl *mp = &default_draw_mtrl;
+    /* Test whether the material flags exclude f0 and include f1. */
 
-    int ri;
+    return ((mp->base->fl & pass_in[p]) == pass_in[p] &&
+            (mp->base->fl & pass_ex[p]) == 0);
+}
 
-    /* Render all billboards in the given range. */
+/*---------------------------------------------------------------------------*/
 
-    if (draw && draw->base)
-    {
-        glDisable(GL_LIGHTING);
-        glDepthMask(GL_FALSE);
-        {
-            for (ri = 0; ri < draw->base->rc; ri++)
-                if (n <= draw->base->rv[ri].d && draw->base->rv[ri].d < f)
-                    mp = sol_back_bill(draw, draw->base->rv + ri, mp, t);
+static int sol_count_geom(const struct s_base *base, int g0, int gc, int mi)
+{
+    int gi, c = 0;
 
-            mp = sol_draw_mtrl(draw, &default_draw_mtrl, mp);
-        }
-        glDepthMask(GL_TRUE);
-        glEnable(GL_LIGHTING);
-    }
-}
+    /* The arguments g0 and gc specify a range of the index array. These     */
+    /* indices refer to geoms. Determine how many of these geoms use the     */
+    /* given material                                                        */
 
-/*---------------------------------------------------------------------------*/
-/*
- * The  following code  renders a  body in  a  ludicrously inefficient
- * manner.  It iterates the materials and scans the data structure for
- * geometry using each.  This  has the effect of absolutely minimizing
- * material  changes,  texture  bindings,  and  Begin/End  pairs,  but
- * maximizing trips through the data.
- *
- * However, this  is only done once  for each level.   The results are
- * stored in display lists.  Thus, it is well worth it.
- */
+    for (gi = 0; gi < gc; gi++)
+        if (base->gv[base->iv[g0 + gi]].mi == mi)
+            c++;
+
+    return c;
+}
 
-static void sol_draw_geom(const struct s_base *base,
-                          const struct b_geom *gp, int mi)
+static int sol_count_body(const struct b_body *bp,
+                          const struct s_base *base, int mi)
 {
-    if (gp->mi == mi)
-    {
-        const float *ui = base->tv[gp->ti].u;
-        const float *uj = base->tv[gp->tj].u;
-        const float *uk = base->tv[gp->tk].u;
+    int li, c = 0;
 
-        const float *ni = base->sv[gp->si].n;
-        const float *nj = base->sv[gp->sj].n;
-        const float *nk = base->sv[gp->sk].n;
+    /* Count all lump geoms with the given material. */
 
-        const float *vi = base->vv[gp->vi].p;
-        const float *vj = base->vv[gp->vj].p;
-        const float *vk = base->vv[gp->vk].p;
+    for (li = 0; li < bp->lc; li++)
+        c += sol_count_geom(base, base->lv[bp->l0 + li].g0,
+                                  base->lv[bp->l0 + li].gc, mi);
 
-        glTexCoord2fv(ui);
-        glNormal3fv(ni);
-        glVertex3fv(vi);
+    /* Count all body geoms with the given material. */
 
-        glTexCoord2fv(uj);
-        glNormal3fv(nj);
-        glVertex3fv(vj);
+    c += sol_count_geom(base, bp->g0, bp->gc, mi);
 
-        glTexCoord2fv(uk);
-        glNormal3fv(nk);
-        glVertex3fv(vk);
-    }
+    return c;
 }
 
-static void sol_draw_lump(const struct s_base *base,
-                          const struct b_lump *lp, int mi)
+static int sol_count_mesh(const struct d_body *bp, int p)
 {
-    int i;
+    int mi, c = 0;
 
-    for (i = 0; i < lp->gc; i++)
-        sol_draw_geom(base, base->gv + base->iv[lp->g0 + i], mi);
+    /* Count the body meshes matching the given material flags. */
+
+    for (mi = 0; mi < bp->mc; ++mi)
+        if (sol_test_mtrl(bp->mv[mi].mp, p))
+            c++;
+
+    return c;
 }
 
-static const struct d_mtrl *sol_draw_body(const struct s_draw *draw,
-                                          const struct b_body *bp,
-                                          const struct d_mtrl *mp,
-                                          int fl, int decal)
+/*---------------------------------------------------------------------------*/
+
+static void sol_mesh_vert(struct d_vert *vp,
+                          const struct s_base *base, int oi)
 {
-    const struct s_base *base = draw->base;
+    /* Gather all vertex attributes for the given offs. */
 
-    int mi, li, gi;
+    const struct b_texc *tq = base->tv + base->ov[oi].ti;
+    const struct b_side *sq = base->sv + base->ov[oi].si;
+    const struct b_vert *vq = base->vv + base->ov[oi].vi;
 
-    /* Iterate all materials of the correct opacity. */
+    vp->p[0] = vq->p[0];
+    vp->p[1] = vq->p[1];
+    vp->p[2] = vq->p[2];
 
-    for (mi = 0; mi < draw->mc; mi++)
+    vp->n[0] = sq->n[0];
+    vp->n[1] = sq->n[1];
+    vp->n[2] = sq->n[2];
+
+    vp->t[0] = tq->u[0];
+    vp->t[1] = tq->u[1];
+}
+
+static void sol_mesh_geom(struct d_vert *vv,   int *vn,
+                          struct d_geom *gv,   int *gn,
+                          const struct s_base *base, int *iv, int g0, int gc, int mi)
+{
+    int gi;
+
+    /* Insert all geoms with material mi into the vertex and element data. */
+
+    for (gi = 0; gi < gc; gi++)
     {
-        struct d_mtrl *mq = draw->mv + mi;
+        const struct b_geom *gq = base->gv + base->iv[g0 + gi];
 
-        if ((mq->base->fl & fl) && (mq->base->fl & M_DECAL) == decal)
+        if (gq->mi == mi)
         {
-            if (sol_enum_mtrl(draw->base, bp, mi))
+            /* Insert a d_vert into the VBO data for each referenced b_off. */
+
+            if (iv[gq->oi] == -1)
+            {
+                iv[gq->oi] = *vn;
+                sol_mesh_vert(vv + (*vn)++, base, gq->oi);
+            }
+            if (iv[gq->oj] == -1)
+            {
+                iv[gq->oj] = *vn;
+                sol_mesh_vert(vv + (*vn)++, base, gq->oj);
+            }
+            if (iv[gq->ok] == -1)
             {
-                /* Set the material state. */
+                iv[gq->ok] = *vn;
+                sol_mesh_vert(vv + (*vn)++, base, gq->ok);
+            }
 
-                mp = sol_draw_mtrl(draw, mq, mp);
+            /* Populate the EBO data using remapped b_off indices. */
 
-                /* Render all geometry of that material. */
+            gv[*gn].i = iv[gq->oi];
+            gv[*gn].j = iv[gq->oj];
+            gv[*gn].k = iv[gq->ok];
 
-                glBegin(GL_TRIANGLES);
-                {
-                    for (li = 0; li < bp->lc; li++)
-                        sol_draw_lump(draw->base,
-                                      base->lv + bp->l0 + li,
-                                      mi);
-                    for (gi = 0; gi < bp->gc; gi++)
-                        sol_draw_geom(draw->base,
-                                      base->gv + base->iv[bp->g0 + gi],
-                                      mi);
-                }
-                glEnd();
-            }
+            (*gn)++;
         }
     }
-
-    return mp;
 }
 
-static void sol_draw_list(const struct s_vary *vary,
-                          const struct v_body *bp, GLuint list)
+static void sol_load_mesh(struct d_mesh *mp,
+                          const struct b_body *bp,
+                          const struct s_draw *draw, int mi)
 {
-    float p[3], e[4], u[3], a;
+    const size_t vs = sizeof (struct d_vert);
+    const size_t gs = sizeof (struct d_geom);
 
-    sol_body_p(p, vary, bp->pi, bp->t);
-    sol_body_e(e, vary, bp, 0);
+    struct d_vert *vv = 0;
+    struct d_geom *gv = 0;
+    int           *iv = 0;
 
-    q_as_axisangle(e, u, &a);
-    a = V_DEG(a);
+    int oc = draw->base->oc;
+    int vn = 0;
+    int gn = 0;
 
-    glPushMatrix();
+    const int gc = sol_count_body(bp, draw->base, mi);
+
+    /* Get temporary storage for vertex and element array creation. */
+
+    if ((vv = (struct d_vert *) calloc(oc, vs)) &&
+        (gv = (struct d_geom *) calloc(gc, gs)) &&
+        (iv = (int           *) calloc(oc, sizeof (int))))
     {
-        /* Translate and rotate a moving body. */
+        int li, i;
 
-        glTranslatef(p[0], p[1], p[2]);
-        glRotatef(a, u[0], u[1], u[2]);
+        /* Initialize the index remapping. */
 
-        /* Draw the body. */
+        for (i = 0; i < oc; ++i) iv[i] = -1;
 
-        glCallList(list);
-    }
-    glPopMatrix();
-}
+        /* Include all matching lump geoms in the arrays. */
 
-void sol_draw(const struct s_draw *draw, int depthmask, int depthtest)
-{
-    int bi;
+        for (li = 0; li < bp->lc; li++)
+            sol_mesh_geom(vv, &vn, gv, &gn, draw->base, iv,
+                          draw->base->lv[bp->l0 + li].g0,
+                          draw->base->lv[bp->l0 + li].gc, mi);
 
-    /* Render all opaque geometry into the color and depth buffers. */
+        /* Include all matching body geoms in the arrays. */
 
-    for (bi = 0; bi < draw->bc; bi++)
-        if (draw->bv[bi].ol)
-            sol_draw_list(draw->vary, draw->vary->bv + bi, draw->bv[bi].ol);
+        sol_mesh_geom(vv, &vn, gv, &gn, draw->base, iv, bp->g0, bp->gc, mi);
 
-    /* Render all translucent geometry into only the color buffer. */
+        /* Initialize buffer objects for all data. */
 
-    if (depthtest == 0) glDisable(GL_DEPTH_TEST);
-    if (depthmask == 0) glDepthMask(GL_FALSE);
-    {
-        for (bi = 0; bi < draw->bc; bi++)
-            if (draw->bv[bi].tl)
-                sol_draw_list(draw->vary, draw->vary->bv + bi, draw->bv[bi].tl);
+        glGenBuffers_(1, &mp->vbo);
+        glBindBuffer_(GL_ARRAY_BUFFER,         mp->vbo);
+        glBufferData_(GL_ARRAY_BUFFER,         vn * vs, vv, GL_STATIC_DRAW);
+        glBindBuffer_(GL_ARRAY_BUFFER,         0);
+
+        glGenBuffers_(1, &mp->ebo);
+        glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, mp->ebo);
+        glBufferData_(GL_ELEMENT_ARRAY_BUFFER, gn * gs, gv, GL_STATIC_DRAW);
+        glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+        mp->mp  = draw->mv + mi;
+        mp->ebc = gn * 3;
     }
-    if (depthmask == 0) glDepthMask(GL_TRUE);
-    if (depthtest == 0) glEnable(GL_DEPTH_TEST);
+
+    free(iv);
+    free(gv);
+    free(vv);
 }
 
-void sol_bill(const struct s_draw *draw, const float *M, float t)
+static void sol_free_mesh(struct d_mesh *mp)
 {
-    const struct d_mtrl *mp = &default_draw_mtrl;
+    if (glIsBuffer_(mp->ebo))
+        glDeleteBuffers_(1, &mp->ebo);
+    if (glIsBuffer_(mp->vbo))
+        glDeleteBuffers_(1, &mp->vbo);
+}
 
-    int ri;
+void sol_draw_mesh(const struct d_mesh *mp, struct s_rend *rend, int p)
+{
+    /* If this mesh has material matching the given flags... */
 
-    for (ri = 0; ri < draw->base->rc; ++ri)
+    if (sol_test_mtrl(mp->mp, p))
     {
-        const struct b_bill *rp = draw->base->rv + ri;
+        const size_t s = sizeof (struct d_vert);
+        const GLenum T = GL_FLOAT;
 
-        float T = rp->t * t;
-        float S = fsinf(T);
+        /* Apply the material state. */
 
-        float w  = rp->w [0] + rp->w [1] * T + rp->w [2] * S;
-        float h  = rp->h [0] + rp->h [1] * T + rp->h [2] * S;
-        float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * S;
-        float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * S;
-        float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * S;
+        sol_apply_mtrl(mp->mp, rend);
 
-        mp = sol_draw_mtrl(draw, draw->mv + rp->mi, mp);
+        /* Bind the mesh data. */
 
-        glPushMatrix();
-        {
-            glTranslatef(rp->p[0], rp->p[1], rp->p[2]);
+        glBindBuffer_(GL_ARRAY_BUFFER,         mp->vbo);
+        glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, mp->ebo);
 
-            if (M && ((rp->fl & B_NOFACE) == 0)) glMultMatrixf(M);
+        glVertexPointer  (3, T, s, (GLvoid *) offsetof (struct d_vert, p));
+        glNormalPointer  (   T, s, (GLvoid *) offsetof (struct d_vert, n));
 
-            if (fabsf(rx) > 0.0f) glRotatef(rx, 1.0f, 0.0f, 0.0f);
-            if (fabsf(ry) > 0.0f) glRotatef(ry, 0.0f, 1.0f, 0.0f);
-            if (fabsf(rz) > 0.0f) glRotatef(rz, 0.0f, 0.0f, 1.0f);
+        if (tex_env_stage(TEX_STAGE_SHADOW))
+        {
+            glTexCoordPointer(3, T, s, (GLvoid *) offsetof (struct d_vert, p));
 
-            glBegin(GL_QUADS);
-            {
-                glTexCoord2f(0.0f, 0.0f); glVertex2f(-w / 2, -h / 2);
-                glTexCoord2f(1.0f, 0.0f); glVertex2f(+w / 2, -h / 2);
-                glTexCoord2f(1.0f, 1.0f); glVertex2f(+w / 2, +h / 2);
-                glTexCoord2f(0.0f, 1.0f); glVertex2f(-w / 2, +h / 2);
-            }
-            glEnd();
+            if (tex_env_stage(TEX_STAGE_CLIP))
+                glTexCoordPointer(3, T, s, (GLvoid *) offsetof (struct d_vert, p));
+
+            tex_env_stage(TEX_STAGE_TEXTURE);
         }
-        glPopMatrix();
-    }
+        glTexCoordPointer(2, T, s, (GLvoid *) offsetof (struct d_vert, t));
+
+        /* Draw the mesh. */
 
-    mp = sol_draw_mtrl(draw, &default_draw_mtrl, mp);
+        glDrawElements(GL_TRIANGLES, mp->ebc, GL_UNSIGNED_SHORT, 0);
+    }
 }
 
-void sol_refl(const struct s_draw *draw)
+/*---------------------------------------------------------------------------*/
+
+static void sol_load_body(struct d_body *bp,
+                          const struct b_body *bq,
+                          const struct s_draw *draw)
 {
-    int bi;
+    int mi;
 
-    /* Render all reflective geometry into the color and depth buffers. */
+    bp->base = bq;
+    bp->mc   =  0;
 
-    for (bi = 0; bi < draw->bc; bi++)
-        if (draw->bv[bi].rl)
-            sol_draw_list(draw->vary, draw->vary->bv + bi, draw->bv[bi].rl);
-}
+    /* Determine how many materials this body uses. */
 
-/*---------------------------------------------------------------------------*/
+    for (mi = 0; mi < draw->mc; ++mi)
+        if (sol_count_body(bq, draw->base, mi))
+            bp->mc++;
 
-static void sol_shad_geom(const struct s_base *base,
-                          const struct b_geom *gp, int mi)
-{
-    if (gp->mi == mi)
+    /* Allocate and initialize a mesh for each material. */
+
+    if ((bp->mv = (struct d_mesh *) calloc(bp->mc, sizeof (struct d_mesh))))
     {
-        const float *vi = base->vv[gp->vi].p;
-        const float *vj = base->vv[gp->vj].p;
-        const float *vk = base->vv[gp->vk].p;
+        int mj = 0;
 
-        glVertex3fv(vi);
-        glVertex3fv(vj);
-        glVertex3fv(vk);
+        for (mi = 0; mi < draw->mc; ++mi)
+            if (sol_count_body(bq, draw->base, mi))
+                sol_load_mesh(bp->mv + mj++, bq, draw, mi);
     }
+
+    /* Cache a mesh count for each pass. */
+
+    bp->pass[0] = sol_count_mesh(bp, 0);
+    bp->pass[1] = sol_count_mesh(bp, 1);
+    bp->pass[2] = sol_count_mesh(bp, 2); 
+    bp->pass[3] = sol_count_mesh(bp, 3); 
+    bp->pass[4] = sol_count_mesh(bp, 4);
+}
+
+static void sol_free_body(struct d_body *bp)
+{
+    int mi;
+
+    for (mi = 0; mi < bp->mc; ++mi)
+        sol_free_mesh(bp->mv + mi);
+
+    free(bp->mv);
 }
 
-static void sol_shad_lump(const struct s_base *base,
-                          const struct b_lump *lp, int mi)
+static void sol_draw_body(const struct d_body *bp, struct s_rend *rend, int p)
 {
     int i;
 
-    for (i = 0; i < lp->gc; i++)
-        sol_shad_geom(base, base->gv + base->iv[lp->g0 + i], mi);
+    for (i = 0; i < bp->mc; ++i)
+        sol_draw_mesh(bp->mv + i, rend, p);
 }
 
-static void sol_shad_body(const struct s_base *base,
-                          const struct b_body *bp,
-                          int fl, int decal)
+/*---------------------------------------------------------------------------*/
+
+int sol_load_draw(struct s_draw *draw, const struct s_vary *vary, int s)
 {
-    int mi, li, gi;
+    int i;
 
-    if (decal)
-    {
-        glEnable(GL_POLYGON_OFFSET_FILL);
-        glPolygonOffset(-1.0f, -2.0f);
-    }
+    memset(draw, 0, sizeof (struct s_draw));
+
+    draw->vary = vary;
+    draw->base = vary->base;
 
-    glBegin(GL_TRIANGLES);
+    /* Initialize all materials for this file. */
+
+    if (draw->base->mc)
     {
-        for (mi = 0; mi < base->mc; mi++)
+        if ((draw->mv = calloc(draw->base->mc, sizeof (*draw->mv))))
         {
-            struct b_mtrl *mp = base->mv + mi;
+            draw->mc = draw->base->mc;
 
-            if ((mp->fl & fl) && (mp->fl & M_DECAL) == decal)
+            for (i = 0; i < draw->mc; i++)
             {
-                for (li = 0; li < bp->lc; li++)
-                    sol_shad_lump(base, base->lv + bp->l0 + li, mi);
-                for (gi = 0; gi < bp->gc; gi++)
-                    sol_shad_geom(base, base->gv + base->iv[bp->g0 + gi], mi);
+                sol_load_mtrl(draw->mv + i, draw->base->mv + i);
+
+                /* If at least one material is reflective, mark it. */
+
+                if (draw->base->mv[i].fl & M_REFLECTIVE)
+                    draw->reflective = 1;
             }
         }
     }
-    glEnd();
-
-    if (decal)
-        glDisable(GL_POLYGON_OFFSET_FILL);
-}
 
-static void sol_shad_list(const struct s_vary *vary,
-                          const struct v_body *bp, GLuint list)
-{
-    float p[3], e[4], u[3], a;
+    /* Initialize shadow state. */
 
-    float X[] = { 1.0f, 0.0f, 0.0f, 0.0f };
-    float Z[] = { 0.0f, 0.0f, 1.0f, 0.0f };
+    draw->shadow_ui = -1;
 
-    sol_body_p(p, vary, bp->pi, bp->t);
-    sol_body_e(e, vary, bp, 0);
+    /* Initialize all bodies for this file. */
 
-    if (e[0] != 1.0f)
+    if (draw->base->bc)
     {
-        q_as_axisangle(e, u, &a);
-        a = V_DEG(a);
+        if ((draw->bv = calloc(draw->base->bc, sizeof (*draw->bv))))
+        {
+            draw->bc = draw->base->bc;
 
-        q_conj(e, e);
-        q_rot(X, e, X);
-        q_rot(Z, e, Z);
+            for (i = 0; i < draw->bc; i++)
+                sol_load_body(draw->bv + i, draw->base->bv + i, draw);
+        }
     }
-    else
-    {
-        u[0] = 0.0f;
-        u[1] = 0.0f;
-        u[2] = 0.0f;
 
-        a = 0.0f;
-    }
+    sol_load_bill(draw);
 
-    glTexGenfv(GL_S, GL_OBJECT_PLANE, X);
-    glTexGenfv(GL_T, GL_OBJECT_PLANE, Z);
+    return 1;
+}
 
-    glPushMatrix();
-    {
-        /* Translate and rotate a moving body. */
+void sol_free_draw(struct s_draw *draw)
+{
+    int i;
 
-        glTranslatef(p[0], p[1], p[2]);
-        glRotatef(a, u[0], u[1], u[2]);
+    sol_free_bill(draw);
 
-        /* Translate the shadow on a moving body. */
+    for (i = 0; i < draw->bc; i++)
+        sol_free_body(draw->bv + i);
+    for (i = 0; i < draw->mc; i++)
+        sol_free_mtrl(draw->mv + i);
+}
 
-        glMatrixMode(GL_TEXTURE);
-        {
-            glPushMatrix();
-            glTranslatef(p[0], p[2], 0.0f);
-        }
-        glMatrixMode(GL_MODELVIEW);
+/*---------------------------------------------------------------------------*/
 
-        /* Draw the body. */
+static void sol_draw_all(const struct s_draw *draw, struct s_rend *rend, int p)
+{
+    int bi;
 
-        glCallList(list);
+    /* Draw all meshes of all bodies matching the given material flags. */
 
-        /* Pop the shadow translation. */
+    for (bi = 0; bi < draw->bc; ++bi)
 
-        glMatrixMode(GL_TEXTURE);
+        if (draw->bv[bi].pass[p])
         {
+            glPushMatrix();
+            {
+                sol_transform(draw->vary, draw->vary->bv + bi, draw->shadow_ui);
+                sol_draw_body(draw->bv + bi, rend, p);
+            }
             glPopMatrix();
         }
-        glMatrixMode(GL_MODELVIEW);
-    }
-    glPopMatrix();
 }
 
-void sol_shad(const struct s_draw *draw)
+void sol_draw_enable(struct s_rend *rend)
 {
-    int bi;
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_NORMAL_ARRAY);
 
-    /* Render all shadowed geometry. */
+    if (gli.max_texture_units > 2)
+        tex_env_active(&tex_env_shadow_clip);
+    else if (gli.max_texture_units > 1)
+        tex_env_active(&tex_env_shadow);
+    else
+        tex_env_active(&tex_env_default);
 
-    glEnable(GL_TEXTURE_GEN_S);
-    glEnable(GL_TEXTURE_GEN_T);
+    if (tex_env_stage(TEX_STAGE_SHADOW))
+    {
+        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 
-    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        if (tex_env_stage(TEX_STAGE_CLIP))
+            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 
-    glDepthMask(GL_FALSE);
-    {
-        for (bi = 0; bi < draw->bc; bi++)
-            if (draw->bv[bi].sl)
-                sol_shad_list(draw->vary, draw->vary->bv + bi, draw->bv[bi].sl);
+        tex_env_stage(TEX_STAGE_TEXTURE);
     }
-    glDepthMask(GL_TRUE);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 
-    glDisable(GL_TEXTURE_GEN_T);
-    glDisable(GL_TEXTURE_GEN_S);
+    rend->mp = &default_draw_mtrl;
 }
 
-/*---------------------------------------------------------------------------*/
-
-static void sol_load_objects(struct s_draw *draw, int s)
+void sol_draw_disable(struct s_rend *rend)
 {
-    int i;
-
-    /* Here we sort geometry into display lists by material type. */
+    sol_apply_mtrl(&default_draw_mtrl, rend);
 
-    for (i = 0; i < draw->bc; i++)
+    if (tex_env_stage(TEX_STAGE_SHADOW))
     {
-        struct d_body *bp = draw->bv + i;
+        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
-        int on = sol_enum_body(draw->base, bp->base, M_OPAQUE);
-        int tn = sol_enum_body(draw->base, bp->base, M_TRANSPARENT);
-        int rn = sol_enum_body(draw->base, bp->base, M_REFLECTIVE);
-        int dn = sol_enum_body(draw->base, bp->base, M_DECAL);
-        int sn = sol_enum_body(draw->base, bp->base, M_SHADOWED);
+        if (tex_env_stage(TEX_STAGE_CLIP))
+            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
-        /* Draw all opaque geometry, decals last. */
-
-        if (on)
-        {
-            bp->ol = glGenLists(1);
+        tex_env_stage(TEX_STAGE_TEXTURE);
+    }
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
-            glNewList(bp->ol, GL_COMPILE);
-            {
-                const struct d_mtrl *mp = &default_draw_mtrl;
+    tex_env_active(&tex_env_default);
 
-                mp = sol_draw_body(draw, bp->base, mp, M_OPAQUE, 0);
-                mp = sol_draw_body(draw, bp->base, mp, M_OPAQUE, M_DECAL);
-                mp = sol_draw_mtrl(draw, &default_draw_mtrl, mp);
-            }
-            glEndList();
-        }
-        else bp->ol = 0;
+    glDisableClientState(GL_NORMAL_ARRAY);
+    glDisableClientState(GL_VERTEX_ARRAY);
+}
 
-        /* Draw all translucent geometry, decals first. */
+/*---------------------------------------------------------------------------*/
 
-        if (tn)
-        {
-            bp->tl = glGenLists(1);
+void sol_draw(const struct s_draw *draw, struct s_rend *rend, int mask, int test)
+{
+    /* Render all opaque geometry, decals last. */
 
-            glNewList(bp->tl, GL_COMPILE);
-            {
-                const struct d_mtrl *mp = &default_draw_mtrl;
+    sol_draw_all(draw, rend, 0);
+    sol_draw_all(draw, rend, 1);
 
-                mp = sol_draw_body(draw, bp->base, mp, M_TRANSPARENT, M_DECAL);
-                mp = sol_draw_body(draw, bp->base, mp, M_TRANSPARENT, 0);
-                mp = sol_draw_mtrl(draw, &default_draw_mtrl, mp);
-            }
-            glEndList();
-        }
-        else bp->tl = 0;
+    /* Render all transparent geometry, decals first. */
 
-        /* Draw all reflective geometry. */
+    if (!test) glDisable(GL_DEPTH_TEST);
+    if (!mask) glDepthMask(GL_FALSE);
+    {
+        sol_draw_all(draw, rend, 2);
+        sol_draw_all(draw, rend, 3);
+    }
+    if (!mask) glDepthMask(GL_TRUE);
+    if (!test) glEnable(GL_DEPTH_TEST);
 
-        if (rn)
-        {
-            bp->rl = glGenLists(1);
+    /* Revert the buffer object state. */
 
-            glNewList(bp->rl, GL_COMPILE);
-            {
-                const struct d_mtrl *mp = &default_draw_mtrl;
+    glBindBuffer_(GL_ARRAY_BUFFER,         0);
+    glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
 
-                mp = sol_draw_body(draw, bp->base, mp, M_REFLECTIVE, 0);
-                mp = sol_draw_mtrl(draw, &default_draw_mtrl, mp);
-            }
-            glEndList();
+void sol_refl(const struct s_draw *draw, struct s_rend *rend)
+{
+    /* Render all reflective geometry. */
 
-            draw->reflective = 1;
-        }
-        else bp->rl = 0;
+    sol_draw_all(draw, rend, 4);
 
-        /* Draw all shadowed geometry. */
+    /* Revert the buffer object state. */
 
-        if (s && (on || rn || sn))
-        {
-            bp->sl = glGenLists(1);
+    glBindBuffer_(GL_ARRAY_BUFFER,         0);
+    glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
 
-            glNewList(bp->sl, GL_COMPILE);
-            {
-                if (on) sol_shad_body(draw->base, bp->base, M_OPAQUE, 0);
-                if (rn) sol_shad_body(draw->base, bp->base, M_REFLECTIVE, 0);
-                if (dn) sol_shad_body(draw->base, bp->base, M_OPAQUE, M_DECAL);
-                if (sn)
-                {
-                    /* Transparent shadowed geometry hack. */
+void sol_back(const struct s_draw *draw,
+              struct s_rend *rend,
+              float n, float f, float t)
+{
+    if (!draw || !draw->base)
+        return;
 
-                    if (dn)
-                        sol_shad_body(draw->base, bp->base, M_SHADOWED, M_DECAL);
+    glDisable(GL_LIGHTING);
+    glDepthMask(GL_FALSE);
 
-                    sol_shad_body(draw->base, bp->base, M_SHADOWED, 0);
-                }
-            }
-            glEndList();
-        }
-        else bp->sl = 0;
-    }
-}
+    sol_bill_enable(draw);
+    {
+        int ri;
 
-static GLuint sol_find_texture(const char *name)
-{
-    char png[MAXSTR];
-    char jpg[MAXSTR];
+        /* Consider each billboard. */
 
-    GLuint o;
+        for (ri = 0; ri < draw->base->rc; ri++)
+        {
+            const struct b_bill *rp = draw->base->rv + ri;
 
-    /* Prefer a lossless copy of the texture over a lossy compression. */
+            /* Render only billboards at distances between n and f. */
 
-    strncpy(png, name, PATHMAX); strcat(png, ".png");
-    strncpy(jpg, name, PATHMAX); strcat(jpg, ".jpg");
+            if (n <= rp->d && rp->d < f)
+            {
+                float T = (rp->t > 0.0f) ? (fmodf(t, rp->t) - rp->t / 2) : 0;
 
-    /* Check for a PNG. */
+                float w = rp->w[0] + rp->w[1] * T + rp->w[2] * T * T;
+                float h = rp->h[0] + rp->h[1] * T + rp->h[2] * T * T;
 
-    if ((o = make_image_from_file(png)))
-        return o;
+                /* Render only billboards facing the viewer. */
 
-    /* Check for a JPG. */
+                if (w > 0 && h > 0)
+                {
+                    float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * T * T;
+                    float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * T * T;
+                    float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * T * T;
 
-    if ((o = make_image_from_file(jpg)))
-        return o;
+                    glPushMatrix();
+                    {
+                        if (ry) glRotatef(ry, 0.0f, 1.0f, 0.0f);
+                        if (rx) glRotatef(rx, 1.0f, 0.0f, 0.0f);
 
-    return 0;
-}
+                        glTranslatef(0.0f, 0.0f, -rp->d);
 
-static void sol_load_textures(struct s_draw *draw)
-{
-    int i;
+                        if (rp->fl & B_FLAT)
+                        {
+                            glRotatef(-rx - 90.0f, 1.0f, 0.0f, 0.0f);
+                            glRotatef(-ry,         0.0f, 0.0f, 1.0f);
+                        }
+                        if (rp->fl & B_EDGE)
+                            glRotatef(-rx,         1.0f, 0.0f, 0.0f);
 
-    /* Load the image referenced by each material. */
+                        if (rz) glRotatef(rz, 0.0f, 0.0f, 1.0f);
 
-    for (i = 0; i < draw->mc; i++)
-    {
-        struct d_mtrl *mp = draw->mv + i;
+                        glScalef(w, h, 1.0f);
 
-        if ((mp->o = sol_find_texture(_(mp->base->f))))
-        {
-            /* Set the texture to clamp or repeat based on material type. */
+                        sol_apply_mtrl(draw->mv + rp->mi, rend);
 
-            if (mp->base->fl & M_CLAMPED)
-            {
-                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
-                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
-            }
-            else
-            {
-                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+                        if (rp->fl & B_EDGE)
+                            glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
+                        else
+                            glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
+                    }
+                    glPopMatrix();
+                }
             }
         }
     }
-}
+    sol_bill_disable();
 
-/*---------------------------------------------------------------------------*/
+    glDepthMask(GL_TRUE);
+    glEnable(GL_LIGHTING);
+}
 
-int sol_load_draw(struct s_draw *draw, const struct s_vary *vary, int s)
+void sol_bill(const struct s_draw *draw,
+              struct s_rend *rend, const float *M, float t)
 {
-    int i;
+    sol_bill_enable(draw);
+    {
+        int ri;
 
-    memset(draw, 0, sizeof (*draw));
+        for (ri = 0; ri < draw->base->rc; ++ri)
+        {
+            const struct b_bill *rp = draw->base->rv + ri;
 
-    draw->vary = vary;
-    draw->base = draw->vary->base;
+            float T = rp->t * t;
+            float S = fsinf(T);
 
-    if (draw->base->mc)
-    {
-        draw->mv = calloc(draw->base->mc, sizeof (*draw->mv));
-        draw->mc = draw->base->mc;
+            float w  = rp->w [0] + rp->w [1] * T + rp->w [2] * S;
+            float h  = rp->h [0] + rp->h [1] * T + rp->h [2] * S;
+            float rx = rp->rx[0] + rp->rx[1] * T + rp->rx[2] * S;
+            float ry = rp->ry[0] + rp->ry[1] * T + rp->ry[2] * S;
+            float rz = rp->rz[0] + rp->rz[1] * T + rp->rz[2] * S;
 
-        for (i = 0; i < draw->base->mc; i++)
-        {
-            struct d_mtrl *mp = draw->mv + i;
-            struct b_mtrl *mq = draw->base->mv + i;
+            sol_apply_mtrl(draw->mv + rp->mi, rend);
 
-            mp->base = mq;
-        }
-    }
+            glPushMatrix();
+            {
+                glTranslatef(rp->p[0], rp->p[1], rp->p[2]);
 
-    if (draw->base->bc)
-    {
-        draw->bv = calloc(draw->base->bc, sizeof (*draw->bv));
-        draw->bc = draw->base->bc;
+                if (M && ((rp->fl & B_NOFACE) == 0)) glMultMatrixf(M);
 
-        for (i = 0; i < draw->base->bc; i++)
-        {
-            struct d_body *bp = draw->bv + i;
-            struct b_body *bq = draw->base->bv + i;
+                if (fabsf(rx) > 0.0f) glRotatef(rx, 1.0f, 0.0f, 0.0f);
+                if (fabsf(ry) > 0.0f) glRotatef(ry, 0.0f, 1.0f, 0.0f);
+                if (fabsf(rz) > 0.0f) glRotatef(rz, 0.0f, 0.0f, 1.0f);
 
-            bp->base = bq;
+                sol_draw_bill(w, h, GL_FALSE);
+            }
+            glPopMatrix();
         }
     }
-
-    sol_load_textures(draw);
-    sol_load_objects (draw, s);
-
-    return 1;
+    sol_bill_disable();
 }
 
-void sol_free_draw(struct s_draw *draw)
+void sol_fade(const struct s_draw *draw, float k)
 {
-    int i;
-
-    for (i = 0; i < draw->mc; i++)
+    if (k > 0.0f)
     {
-        if (glIsTexture(draw->mv[i].o))
-            glDeleteTextures(1, &draw->mv[i].o);
-    }
+        glMatrixMode(GL_PROJECTION);
+        glPushMatrix();
+        glLoadIdentity();
+        glMatrixMode(GL_MODELVIEW);
+        glPushMatrix();
+        glLoadIdentity();
+        {
+            glEnable(GL_COLOR_MATERIAL);
+            glDisable(GL_LIGHTING);
+            glDisable(GL_DEPTH_TEST);
+            glDisable(GL_TEXTURE_2D);
 
-    for (i = 0; i < draw->bc; i++)
-    {
-        if (glIsList(draw->bv[i].ol))
-            glDeleteLists(draw->bv[i].ol, 1);
-        if (glIsList(draw->bv[i].tl))
-            glDeleteLists(draw->bv[i].tl, 1);
-        if (glIsList(draw->bv[i].rl))
-            glDeleteLists(draw->bv[i].rl, 1);
-        if (glIsList(draw->bv[i].sl))
-            glDeleteLists(draw->bv[i].sl, 1);
-    }
+            glColor4f(0.0f, 0.0f, 0.0f, k);
+
+            sol_bill_enable(draw);
+            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+            sol_bill_disable();
 
-    free(draw->mv);
-    free(draw->bv);
+            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
 
-    memset(draw, 0, sizeof (*draw));
+            glEnable(GL_TEXTURE_2D);
+            glEnable(GL_DEPTH_TEST);
+            glEnable(GL_LIGHTING);
+            glDisable(GL_COLOR_MATERIAL);
+        }
+        glMatrixMode(GL_PROJECTION);
+        glPopMatrix();
+        glMatrixMode(GL_MODELVIEW);
+        glPopMatrix();
+    }
 }
 
 /*---------------------------------------------------------------------------*/