share/gui: don't use less of widget width for truncation than available
[neverball] / share / mapc.c
index d2f1546..3117147 100644 (file)
@@ -1,4 +1,4 @@
-/*   
+/*
  * Copyright (C) 2003 Robert Kooima
  *
  * NEVERBALL is  free software; you can redistribute  it and/or modify
 
 /*---------------------------------------------------------------------------*/
 
-#ifdef WIN32
-#pragma comment(lib, "SDL_ttf.lib")
-#pragma comment(lib, "SDL_image.lib")
-#pragma comment(lib, "SDL_mixer.lib")
-#pragma comment(lib, "SDL.lib")
-#pragma comment(lib, "SDLmain.lib")
-#pragma comment(lib, "opengl32.lib")
-#endif
-
-/*---------------------------------------------------------------------------*/
-
-#include <SDL.h>
-#include <SDL_image.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "vec3.h"
 #include "solid.h"
+#include "base_image.h"
 #include "base_config.h"
+#include "fs.h"
+#include "common.h"
 
 #define MAXSTR 256
 #define MAXKEY 16
 
 /*---------------------------------------------------------------------------*/
 
+static const char *input_file;
+static int         debug_output = 0;
+
+/*---------------------------------------------------------------------------*/
+
 /* Ohhhh... arbitrary! */
 
-#define MAXM    256
-#define MAXV    32767
-#define MAXE    32767
-#define MAXS    32767
-#define MAXT    32767
-#define MAXG    32767
-#define MAXL    1024
-#define MAXN    1024
-#define MAXP    512
-#define MAXB    512
-#define MAXC    1024
-#define MAXZ    16
-#define MAXJ    32
-#define MAXX    16
-#define MAXR    1024
-#define MAXU    16
-#define MAXW    32
-#define MAXD    128
-#define MAXA    8192
-#define MAXI    32767
+#define MAXM    1024
+#define MAXV    65536
+#define MAXE    65536
+#define MAXS    65536
+#define MAXT    131072
+#define MAXG    65536
+#define MAXL    4096
+#define MAXN    2048
+#define MAXP    2048
+#define MAXB    1024
+#define MAXH    2048
+#define MAXZ    1024
+#define MAXJ    1024
+#define MAXX    1024
+#define MAXR    2048
+#define MAXU    1024
+#define MAXW    1024
+#define MAXD    1024
+#define MAXA    16384
+#define MAXI    262144
 
 static int overflow(const char *s)
 {
@@ -131,14 +126,14 @@ static int incb(struct s_file *fp)
     return (fp->bc < MAXB) ? fp->bc++ : overflow("body");
 }
 
-static int incc(struct s_file *fp)
+static int inch(struct s_file *fp)
 {
-    return (fp->cc < MAXC) ? fp->cc++ : overflow("coin");
+    return (fp->hc < MAXH) ? fp->hc++ : overflow("item");
 }
 
 static int incz(struct s_file *fp)
 {
-    return (fp->zc < MAXZ) ? fp->zc++ : overflow("geol");
+    return (fp->zc < MAXZ) ? fp->zc++ : overflow("goal");
 }
 
 static int incj(struct s_file *fp)
@@ -166,6 +161,11 @@ static int incw(struct s_file *fp)
     return (fp->wc < MAXW) ? fp->wc++ : overflow("view");
 }
 
+static int incd(struct s_file *fp)
+{
+    return (fp->dc < MAXD) ? fp->dc++ : overflow("dict");
+}
+
 static int inci(struct s_file *fp)
 {
     return (fp->ic < MAXI) ? fp->ic++ : overflow("indx");
@@ -183,13 +183,14 @@ static void init_file(struct s_file *fp)
     fp->nc = 0;
     fp->pc = 0;
     fp->bc = 0;
-    fp->cc = 0;
+    fp->hc = 0;
     fp->zc = 0;
     fp->jc = 0;
     fp->xc = 0;
     fp->rc = 0;
     fp->uc = 0;
     fp->wc = 0;
+    fp->dc = 0;
     fp->ac = 0;
     fp->ic = 0;
 
@@ -203,13 +204,14 @@ static void init_file(struct s_file *fp)
     fp->nv = (struct s_node *) calloc(MAXN, sizeof (struct s_node));
     fp->pv = (struct s_path *) calloc(MAXP, sizeof (struct s_path));
     fp->bv = (struct s_body *) calloc(MAXB, sizeof (struct s_body));
-    fp->cv = (struct s_coin *) calloc(MAXC, sizeof (struct s_coin));
+    fp->hv = (struct s_item *) calloc(MAXH, sizeof (struct s_item));
     fp->zv = (struct s_goal *) calloc(MAXZ, sizeof (struct s_goal));
     fp->jv = (struct s_jump *) calloc(MAXJ, sizeof (struct s_jump));
     fp->xv = (struct s_swch *) calloc(MAXX, sizeof (struct s_swch));
     fp->rv = (struct s_bill *) calloc(MAXR, sizeof (struct s_bill));
     fp->uv = (struct s_ball *) calloc(MAXU, sizeof (struct s_ball));
     fp->wv = (struct s_view *) calloc(MAXW, sizeof (struct s_view));
+    fp->dv = (struct s_dict *) calloc(MAXD, sizeof (struct s_dict));
     fp->av = (char          *) calloc(MAXA, sizeof (char));
     fp->iv = (int           *) calloc(MAXI, sizeof (int));
 }
@@ -224,7 +226,7 @@ static void init_file(struct s_file *fp)
  * and fills waiting ints with the proper values.
  */
 
-#define MAXSYM 1024
+#define MAXSYM 2048
 
 static char symv[MAXSYM][MAXSTR];
 static int  valv[MAXSYM];
@@ -291,7 +293,7 @@ static void targets(struct s_file *fp)
  * The following code caches  image sizes.  Textures are referenced by
  * name,  but  their  sizes   are  necessary  when  computing  texture
  * coordinates.  This code  allows each file to be  accessed only once
- * regardless of the number of surfaces refering to it.
+ * regardless of the number of surfaces referring to it.
  */
 
 struct _imagedata
@@ -322,15 +324,11 @@ static void free_imagedata()
 
 static int size_load(const char *file, int *w, int *h)
 {
-    SDL_Surface *S;
+    void *p;
 
-    if ((S = IMG_Load(file)))
+    if ((p = image_load(file, w, h, NULL)))
     {
-        *w = S->w;
-        *h = S->h;
-
-        SDL_FreeSurface(S);
-
+        free(p);
         return 1;
     }
     return 0;
@@ -339,7 +337,6 @@ static int size_load(const char *file, int *w, int *h)
 static void size_image(const char *name, int *w, int *h)
 {
     char jpg[MAXSTR];
-    char tga[MAXSTR];
     char png[MAXSTR];
     int i;
 
@@ -357,12 +354,10 @@ static void size_image(const char *name, int *w, int *h)
     *h = 0;
 
     strcpy(jpg, name); strcat(jpg, ".jpg");
-    strcpy(tga, name); strcat(tga, ".tga");
     strcpy(png, name); strcat(png, ".png");
 
-    if (size_load(config_data(png), w, h) ||
-        size_load(config_data(tga), w, h) ||
-        size_load(config_data(jpg), w, h))
+    if (size_load(png, w, h) ||
+        size_load(jpg, w, h))
     {
 
         if (image_n + 1 >= image_alloc)
@@ -396,10 +391,15 @@ static void size_image(const char *name, int *w, int *h)
 
 /* Read the given material file, adding a new material to the solid.  */
 
+#define scan_vec4(f, s, v)                                              \
+    if (fs_gets((s), sizeof (s), (f)))                                  \
+        sscanf((s), "%f %f %f %f", (v), (v) + 1, (v) + 2, (v) + 3)
+
 static int read_mtrl(struct s_file *fp, const char *name)
 {
+    static char line[MAXSTR];
     struct s_mtrl *mp;
-    FILE *fin;
+    fs_file fin;
     int mi;
 
     for (mi = 0; mi < fp->mc; mi++)
@@ -410,32 +410,41 @@ static int read_mtrl(struct s_file *fp, const char *name)
 
     strncpy(mp->f, name, PATHMAX - 1);
 
-    mp->a[0] = mp->a[1] = mp->a[2] = mp->a[3] = 1.0f;
-    mp->d[0] = mp->d[1] = mp->d[2] = mp->d[3] = 1.0f;
-    mp->s[0] = mp->s[1] = mp->s[2] = mp->s[3] = 1.0f;
-    mp->e[0] = mp->e[1] = mp->e[2] = mp->e[3] = 1.0f;
+    mp->a[0] = mp->a[1] = mp->a[2] = 0.2f;
+    mp->d[0] = mp->d[1] = mp->d[2] = 0.8f;
+    mp->s[0] = mp->s[1] = mp->s[2] = 0.0f;
+    mp->e[0] = mp->e[1] = mp->e[2] = 0.0f;
+    mp->a[3] = mp->d[3] = mp->s[3] = mp->e[3] = 1.0f;
     mp->h[0] = 0.0f;
     mp->fl   = 0;
+    mp->angle = 45.0f;
 
-    if ((fin = fopen(config_data(name), "r")))
+    if ((fin = fs_open(name, "r")))
     {
-        fscanf(fin,
-               "%f %f %f %f "
-               "%f %f %f %f "
-               "%f %f %f %f "
-               "%f %f %f %f "
-               "%f %d ",
-               mp->d, mp->d + 1, mp->d + 2, mp->d + 3,
-               mp->a, mp->a + 1, mp->a + 2, mp->a + 3,
-               mp->s, mp->s + 1, mp->s + 2, mp->s + 3,
-               mp->e, mp->e + 1, mp->e + 2, mp->e + 3,
-               mp->h, &mp->fl);
-        fclose(fin);
+        scan_vec4(fin, line, mp->d);
+        scan_vec4(fin, line, mp->a);
+        scan_vec4(fin, line, mp->s);
+        scan_vec4(fin, line, mp->e);
+
+        if (fs_gets(line, sizeof (line), fin))
+            mp->h[0] = strtod(line, NULL);
+
+        if (fs_gets(line, sizeof (line), fin))
+            mp->fl = strtol(line, NULL, 10);
+
+        if (fs_gets(line, sizeof (line), fin))
+            mp->angle = strtod(line, NULL);
+
+        fs_close(fin);
     }
+    else
+        fprintf(stderr, "%s: unknown material \"%s\"\n", input_file, name);
 
     return mi;
 }
 
+#undef scan_vec4
+
 /*---------------------------------------------------------------------------*/
 
 /*
@@ -470,10 +479,34 @@ static void move_lump(struct s_file *fp,
 static void move_body(struct s_file *fp,
                       struct s_body *bp)
 {
-    int i;
+    int i, *b;
+
+    /* Move the lumps. */
 
     for (i = 0; i < bp->lc; i++)
         move_lump(fp, fp->lv + bp->l0 + i, fp->pv[bp->pi].p);
+
+    /* Create an array to mark any verts referenced by moved geoms. */
+
+    if (bp->gc > 0 && (b = (int *) calloc(fp->vc, sizeof (int))))
+    {
+        /* Mark the verts. */
+
+        for (i = 0; i < bp->gc; i++)
+        {
+            b[fp->gv[fp->iv[bp->g0 + i]].vi] = 1;
+            b[fp->gv[fp->iv[bp->g0 + i]].vj] = 1;
+            b[fp->gv[fp->iv[bp->g0 + i]].vk] = 1;
+        }
+
+        /* Apply the motion to the marked vertices. */
+
+        for (i = 0; i < fp->vc; ++i)
+            if (b[i])
+                move_vert(fp->vv + i, fp->pv[bp->pi].p);
+
+        free(b);
+    }
 }
 
 static void move_file(struct s_file *fp)
@@ -538,24 +571,23 @@ static void read_f(struct s_file *fp, const char *line,
     gp->si += (s0 - 1);
     gp->sj += (s0 - 1);
     gp->sk += (s0 - 1);
-    
+
     gp->mi  = mi;
 }
 
-static void read_obj(struct s_file *fp, const char *name)
+static void read_obj(struct s_file *fp, const char *name, int mi)
 {
     char line[MAXSTR];
     char mtrl[MAXSTR];
-    FILE *fin;
+    fs_file fin;
 
     int v0 = fp->vc;
     int t0 = fp->tc;
     int s0 = fp->sc;
-    int mi = 0;
 
-    if ((fin = fopen(config_data(name), "r")))
+    if ((fin = fs_open(name, "r")))
     {
-        while (fgets(line, MAXSTR, fin))
+        while (fs_gets(line, MAXSTR, fin))
         {
             if (strncmp(line, "usemtl", 6) == 0)
             {
@@ -565,7 +597,7 @@ static void read_obj(struct s_file *fp, const char *name)
 
             else if (strncmp(line, "f", 1) == 0)
             {
-                if (fp->mv[mi].d[3] > 0)
+                if (fp->mv[mi].d[3] > 0.0f)
                     read_f(fp, line + 1, v0, t0, s0, mi);
             }
 
@@ -573,7 +605,7 @@ static void read_obj(struct s_file *fp, const char *name)
             else if (strncmp(line, "vn", 2) == 0) read_vn(fp, line + 2);
             else if (strncmp(line, "v",  1) == 0) read_v (fp, line + 1);
         }
-        fclose(fin);
+        fs_close(fin);
     }
 }
 
@@ -587,19 +619,19 @@ static float plane_v[MAXS][3];
 static int   plane_f[MAXS];
 static int   plane_m[MAXS];
 
-static void make_plane(int pi, int x0, int y0, int z0,
-                       int x1, int y1, int z1,
-                       int x2, int y2, int z2,
-                       int tu, int tv, int r,
-                       float su, float sv, int fl, const char *s)
+static void make_plane(int   pi, float x0, float y0, float      z0,
+                       float x1, float y1, float z1,
+                       float x2, float y2, float z2,
+                       float tu, float tv, float r,
+                       float su, float sv, int   fl, const char *s)
 {
     static const float base[6][3][3] = {
-        {{  0,  0,  1 }, {  1,  0,  0 }, {  0, -1,  0 }},
-        {{  0,  0, -1 }, {  1,  0,  0 }, {  0, -1,  0 }},
-        {{  1,  0,  0 }, {  0,  0, -1 }, {  0, -1,  0 }},
-        {{ -1,  0,  0 }, {  0,  0, -1 }, {  0, -1,  0 }},
-        {{  0,  1,  0 }, {  1,  0,  0 }, {  0,  0,  1 }},
-        {{  0, -1,  0 }, {  1,  0,  0 }, {  0,  0,  1 }},
+        {{  0,  0,  1 }, {  1,  0,  0 }, {  0,  1,  0 }},
+        {{  0,  0, -1 }, {  1,  0,  0 }, {  0,  1,  0 }},
+        {{  1,  0,  0 }, {  0,  0, -1 }, {  0,  1,  0 }},
+        {{ -1,  0,  0 }, {  0,  0, -1 }, {  0,  1,  0 }},
+        {{  0,  1,  0 }, {  1,  0,  0 }, {  0,  0, -1 }},
+        {{  0, -1,  0 }, {  1,  0,  0 }, {  0,  0, -1 }},
     };
 
     float R[16];
@@ -613,24 +645,24 @@ static void make_plane(int pi, int x0, int y0, int z0,
 
     plane_f[pi] = fl ? L_DETAIL : 0;
 
-    p0[0] = +(float) x0 / SCALE;
-    p0[1] = +(float) z0 / SCALE;
-    p0[2] = -(float) y0 / SCALE;
+    p0[0] = +x0 / SCALE;
+    p0[1] = +z0 / SCALE;
+    p0[2] = -y0 / SCALE;
 
-    p1[0] = +(float) x1 / SCALE;
-    p1[1] = +(float) z1 / SCALE;
-    p1[2] = -(float) y1 / SCALE;
+    p1[0] = +x1 / SCALE;
+    p1[1] = +z1 / SCALE;
+    p1[2] = -y1 / SCALE;
 
-    p2[0] = +(float) x2 / SCALE;
-    p2[1] = +(float) z2 / SCALE;
-    p2[2] = -(float) y2 / SCALE;
+    p2[0] = +x2 / SCALE;
+    p2[1] = +z2 / SCALE;
+    p2[2] = -y2 / SCALE;
 
     v_sub(u, p0, p1);
     v_sub(v, p2, p1);
 
     v_crs(plane_n[pi], u, v);
     v_nrm(plane_n[pi], plane_n[pi]);
-        
+
     plane_d[pi] = v_dot(plane_n[pi], p1);
 
     for (i = 0; i < 6; i++)
@@ -644,10 +676,12 @@ static void make_plane(int pi, int x0, int y0, int z0,
     p[1] = 0.f;
     p[2] = 0.f;
 
-    m_rot(R, base[n][0], V_RAD(r));
+    /* Always rotate around the positive axis */
 
-    v_mad(p, p, base[n][1], su * tu / SCALE);
-    v_mad(p, p, base[n][2], sv * tv / SCALE);
+    m_rot(R, base[n - (n % 2)][0], V_RAD(r));
+
+    v_mad(p, p, base[n][1], +su * tu / SCALE);
+    v_mad(p, p, base[n][2], -sv * tv / SCALE);
 
     m_vxfm(plane_u[pi], R, base[n][1]);
     m_vxfm(plane_v[pi], R, base[n][2]);
@@ -669,17 +703,17 @@ static void make_plane(int pi, int x0, int y0, int z0,
 #define T_END 4
 #define T_NOP 5
 
-static int map_token(FILE *fin, int pi, char key[MAXSTR], char val[MAXSTR])
+static int map_token(fs_file fin, int pi, char key[MAXSTR], char val[MAXSTR])
 {
     char buf[MAXSTR];
 
-    if (fgets(buf, MAXSTR, fin))
+    if (fs_gets(buf, MAXSTR, fin))
     {
         char c;
-        int x0, y0, z0;
-        int x1, y1, z1;
-        int x2, y2, z2;
-        int tu, tv, r;
+        float x0, y0, z0;
+        float x1, y1, z1;
+        float x2, y2, z2;
+        float tu, tv, r;
         float su, sv;
         int fl;
 
@@ -690,7 +724,7 @@ static int map_token(FILE *fin, int pi, char key[MAXSTR], char val[MAXSTR])
 
         /* Scan a key-value pair. */
 
-        if (buf[0] == '"')
+        if (buf[0] == '\"')
         {
             strcpy(key, strtok(buf,  "\""));
             (void)      strtok(NULL, "\"");
@@ -702,10 +736,10 @@ static int map_token(FILE *fin, int pi, char key[MAXSTR], char val[MAXSTR])
         /* Scan a plane. */
 
         if (sscanf(buf,
-                   "%c %d %d %d %c "
-                   "%c %d %d %d %c "
-                   "%c %d %d %d %c "
-                   "%s %d %d %d %f %f %d",
+                   "%c %f %f %f %c "
+                   "%c %f %f %f %c "
+                   "%c %f %f %f %c "
+                   "%s %f %f %f %f %f %d",
                    &c, &x0, &y0, &z0, &c,
                    &c, &x1, &y1, &z1, &c,
                    &c, &x2, &y2, &z2, &c,
@@ -729,7 +763,7 @@ static int map_token(FILE *fin, int pi, char key[MAXSTR], char val[MAXSTR])
 
 /* Parse a lump from the given file and add it to the solid. */
 
-static void read_lump(struct s_file *fp, FILE *fin)
+static void read_lump(struct s_file *fp, fs_file fin)
 {
     char k[MAXSTR];
     char v[MAXSTR];
@@ -776,6 +810,7 @@ static void make_path(struct s_file *fp,
     pp->t    = 1.f;
     pp->pi   = pi;
     pp->f    = 1;
+    pp->s    = 1;
 
     for (i = 0; i < c; i++)
     {
@@ -791,33 +826,63 @@ static void make_path(struct s_file *fp,
         if (strcmp(k[i], "speed") == 0)
             sscanf(v[i], "%f", &pp->t);
 
+        if (strcmp(k[i], "smooth") == 0)
+            pp->s = atoi(v[i]);
+
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            pp->p[0] = +(float) x / SCALE;
-            pp->p[1] = +(float) z / SCALE;
-            pp->p[2] = -(float) y / SCALE;
+            pp->p[0] = +x / SCALE;
+            pp->p[1] = +z / SCALE;
+            pp->p[2] = -y / SCALE;
         }
     }
 }
 
+static void make_dict(struct s_file *fp,
+                      const char *k,
+                      const char *v)
+{
+    int space_left, space_needed, di = incd(fp);
+
+    struct s_dict *dp = fp->dv + di;
+
+    space_left   = MAXA - fp->ac;
+    space_needed = strlen(k) + 1 + strlen(v) + 1;
+
+    if (space_needed > space_left)
+    {
+        fp->dc--;
+        return;
+    }
+
+    dp->ai = fp->ac;
+    dp->aj = dp->ai + strlen(k) + 1;
+    fp->ac = dp->aj + strlen(v) + 1;
+
+    strncpy(fp->av + dp->ai, k, space_left);
+    strncpy(fp->av + dp->aj, v, space_left - strlen(k) - 1);
+}
+
+static int read_dict_entries = 0;
+
 static void make_body(struct s_file *fp,
                       char k[][MAXSTR],
                       char v[][MAXSTR], int c, int l0)
 {
-    int i, bi = incb(fp);
+    int i, mi = 0, bi = incb(fp);
 
     int g0 = fp->gc;
     int v0 = fp->vc;
 
     float p[3];
 
-    int x = 0;
-    int y = 0;
-    int z = 0;
+    float x = 0.f;
+    float y = 0.f;
+    float z = 0.f;
 
     struct s_body *bp = fp->bv + bi;
 
@@ -833,21 +898,17 @@ static void make_body(struct s_file *fp,
         else if (strcmp(k[i], "target") == 0)
             make_ref(v[i], &bp->pi);
 
+        else if (strcmp(k[i], "material") == 0)
+            mi = read_mtrl(fp, v[i]);
+
         else if (strcmp(k[i], "model") == 0)
-            read_obj(fp, v[i]);
+            read_obj(fp, v[i], mi);
 
         else if (strcmp(k[i], "origin") == 0)
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-        else if (strcmp(k[i], "classname") != 0)
-        {
-            /* Considers other strings as metadata */
-            strcat(fp->av, k[i]);
-            strcat(fp->av, "=");
-            strcat(fp->av, v[i]);
-            strcat(fp->av, "\n");
-            fp->ac += (int) (strlen(v[i]) + (strlen(k[i])) + 2);
-        }
+        else if (read_dict_entries && strcmp(k[i], "classname") != 0)
+            make_dict(fp, k[i], v[i]);
     }
 
     bp->l0 = l0;
@@ -858,41 +919,55 @@ static void make_body(struct s_file *fp,
     for (i = 0; i < bp->gc; i++)
         fp->iv[inci(fp)] = g0++;
 
-    p[0] = +(float) x / SCALE;
-    p[1] = +(float) z / SCALE;
-    p[2] = -(float) y / SCALE;
+    p[0] = +x / SCALE;
+    p[1] = +z / SCALE;
+    p[2] = -y / SCALE;
 
     for (i = v0; i < fp->vc; i++)
         v_add(fp->vv[i].p, fp->vv[i].p, p);
+
+    read_dict_entries = 0;
 }
 
-static void make_coin(struct s_file *fp,
+static void make_item(struct s_file *fp,
                       char k[][MAXSTR],
                       char v[][MAXSTR], int c)
 {
-    int i, ci = incc(fp);
+    int i, hi = inch(fp);
+
+    struct s_item *hp = fp->hv + hi;
 
-    struct s_coin *cp = fp->cv + ci;
+    hp->p[0] = 0.f;
+    hp->p[1] = 0.f;
+    hp->p[2] = 0.f;
 
-    cp->p[0] = 0.f;
-    cp->p[1] = 0.f;
-    cp->p[2] = 0.f;
-    cp->n    = 1;
+    hp->t = ITEM_NONE;
+    hp->n = 0;
 
     for (i = 0; i < c; i++)
     {
+        if (strcmp(k[i], "classname") == 0)
+        {
+            if (strcmp(v[i], "light") == 0)
+                hp->t = ITEM_COIN;
+            else if (strcmp(v[i], "item_health_large") == 0)
+                hp->t = ITEM_GROW;
+            else if (strcmp(v[i], "item_health_small") == 0)
+                hp->t = ITEM_SHRINK;
+        }
+
         if (strcmp(k[i], "light") == 0)
-            sscanf(v[i], "%d", &cp->n);
+            sscanf(v[i], "%d", &hp->n);
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            cp->p[0] = +(float) x / SCALE;
-            cp->p[1] = +(float) z / SCALE;
-            cp->p[2] = -(float) y / SCALE;
+            hp->p[0] = +x / SCALE;
+            hp->p[1] = +z / SCALE;
+            hp->p[2] = -y / SCALE;
         }
     }
 }
@@ -937,18 +1012,13 @@ static void make_bill(struct s_file *fp,
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
-            float p[3];
-
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            p[0] = +(float) x / SCALE;
-            p[1] = +(float) z / SCALE;
-            p[2] = -(float) y / SCALE;
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            rp->d     = v_len(p);
-            rp->rx[0] = V_DEG(fatan2f(+p[1], rp->d));
-            rp->ry[0] = V_DEG(fatan2f(+p[0], -p[2]));
+            rp->p[0] = +x / SCALE;
+            rp->p[1] = +z / SCALE;
+            rp->p[2] = -y / SCALE;
         }
     }
 
@@ -968,27 +1038,21 @@ static void make_goal(struct s_file *fp,
     zp->p[1] = 0.f;
     zp->p[2] = 0.f;
     zp->r    = 0.75;
-    zp->s    = 0;
-    zp->c    = 0;
 
     for (i = 0; i < c; i++)
     {
         if (strcmp(k[i], "radius") == 0)
             sscanf(v[i], "%f", &zp->r);
-        if (strcmp(k[i], "skip") == 0)
-            sscanf(v[i], "%d", &zp->s);
-        if (strcmp(k[i], "special") == 0)
-            sscanf(v[i], "%d", &zp->c);
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            zp->p[0] = +(float) (x)      / SCALE;
-            zp->p[1] = +(float) (z - 24) / SCALE;
-            zp->p[2] = -(float) (y)      / SCALE;
+            zp->p[0] = +(x)      / SCALE;
+            zp->p[1] = +(z - 24) / SCALE;
+            zp->p[2] = -(y)      / SCALE;
         }
     }
 }
@@ -1015,13 +1079,13 @@ static void make_view(struct s_file *fp,
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            wp->p[0] = +(float) x / SCALE;
-            wp->p[1] = +(float) z / SCALE;
-            wp->p[2] = -(float) y / SCALE;
+            wp->p[0] = +x / SCALE;
+            wp->p[1] = +z / SCALE;
+            wp->p[2] = -y / SCALE;
         }
     }
 }
@@ -1052,13 +1116,13 @@ static void make_jump(struct s_file *fp,
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            jp->p[0] = +(float) x / SCALE;
-            jp->p[1] = +(float) z / SCALE;
-            jp->p[2] = -(float) y / SCALE;
+            jp->p[0] = +x / SCALE;
+            jp->p[1] = +z / SCALE;
+            jp->p[2] = -y / SCALE;
         }
     }
 }
@@ -1091,23 +1155,29 @@ static void make_swch(struct s_file *fp,
             make_ref(v[i], &xp->pi);
 
         if (strcmp(k[i], "timer") == 0)
+        {
             sscanf(v[i], "%f", &xp->t0);
+            xp->t = xp->t0;
+        }
 
         if (strcmp(k[i], "state") == 0)
-            xp->f = atoi(v[i]);
-        
+        {
+            xp->f  = atoi(v[i]);
+            xp->f0 = atoi(v[i]);
+        }
+
         if (strcmp(k[i], "invisible") == 0)
             xp->i = atoi(v[i]);
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            xp->p[0] = +(float) x / SCALE;
-            xp->p[1] = +(float) z / SCALE;
-            xp->p[2] = -(float) y / SCALE;
+            xp->p[0] = +x / SCALE;
+            xp->p[1] = +z / SCALE;
+            xp->p[2] = -y / SCALE;
         }
     }
 }
@@ -1120,7 +1190,7 @@ static void make_targ(struct s_file *fp,
 
     targ_p[targ_n][0] = 0.f;
     targ_p[targ_n][1] = 0.f;
-    targ_p[targ_n][3] = 0.f;
+    targ_p[targ_n][2] = 0.f;
 
     for (i = 0; i < c; i++)
     {
@@ -1129,13 +1199,13 @@ static void make_targ(struct s_file *fp,
 
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            targ_p[targ_n][0] = +(float) x / SCALE;
-            targ_p[targ_n][1] = +(float) z / SCALE;
-            targ_p[targ_n][2] = -(float) y / SCALE;
+            targ_p[targ_n][0] = +x / SCALE;
+            targ_p[targ_n][1] = +z / SCALE;
+            targ_p[targ_n][2] = -y / SCALE;
         }
     }
 
@@ -1150,47 +1220,25 @@ static void make_ball(struct s_file *fp,
 
     struct s_ball *up = fp->uv + ui;
 
-    up->p[0] = 0.f;
-    up->p[1] = 0.f;
-    up->p[2] = 0.f;
-    up->r    = 0.25;
-
-    up->e[0][0] = 1.f;
-    up->e[0][1] = 0.f;
-    up->e[0][2] = 0.f;
-    up->e[1][0] = 0.f;
-    up->e[1][1] = 1.f;
-    up->e[1][2] = 0.f;
-    up->e[2][0] = 0.f;
-    up->e[2][1] = 0.f;
-    up->e[2][2] = 1.f;
-
-    up->v[0] = 0.f;
-    up->v[1] = 0.f;
-    up->v[2] = 0.f;
-    up->w[0] = 0.f;
-    up->w[1] = 0.f;
-    up->w[2] = 0.f;
-
-    up->a = 90.f;
+    up->p[0] = 0.0f;
+    up->p[1] = 0.0f;
+    up->p[2] = 0.0f;
+    up->r    = 0.25f;
 
     for (i = 0; i < c; i++)
     {
         if (strcmp(k[i], "radius") == 0)
             sscanf(v[i], "%f", &up->r);
 
-        if (strcmp(k[i], "angle") == 0)
-            sscanf(v[i], "%f", &up->a);
-
         if (strcmp(k[i], "origin") == 0)
         {
-            int x = 0, y = 0, z = 0;
+            float x = 0.f, y = 0.f, z = 0.f;
 
-            sscanf(v[i], "%d %d %d", &x, &y, &z);
+            sscanf(v[i], "%f %f %f", &x, &y, &z);
 
-            up->p[0] = +(float) (x)      / SCALE;
-            up->p[1] = +(float) (z - 24) / SCALE;
-            up->p[2] = -(float) (y)      / SCALE;
+            up->p[0] = +(x)      / SCALE;
+            up->p[1] = +(z - 24) / SCALE;
+            up->p[2] = -(y)      / SCALE;
         }
     }
 
@@ -1199,7 +1247,7 @@ static void make_ball(struct s_file *fp,
 
 /*---------------------------------------------------------------------------*/
 
-static void read_ent(struct s_file *fp, FILE *fin)
+static void read_ent(struct s_file *fp, fs_file fin)
 {
     char k[MAXKEY][MAXSTR];
     char v[MAXKEY][MAXSTR];
@@ -1219,7 +1267,9 @@ static void read_ent(struct s_file *fp, FILE *fin)
         if (t == T_END) break;
     }
 
-    if (!strcmp(v[i], "light"))                    make_coin(fp, k, v, c);
+    if (!strcmp(v[i], "light"))                    make_item(fp, k, v, c);
+    if (!strcmp(v[i], "item_health_large"))        make_item(fp, k, v, c);
+    if (!strcmp(v[i], "item_health_small"))        make_item(fp, k, v, c);
     if (!strcmp(v[i], "info_camp"))                make_swch(fp, k, v, c);
     if (!strcmp(v[i], "info_null"))                make_bill(fp, k, v, c);
     if (!strcmp(v[i], "path_corner"))              make_path(fp, k, v, c);
@@ -1228,12 +1278,16 @@ static void read_ent(struct s_file *fp, FILE *fin)
     if (!strcmp(v[i], "info_player_deathmatch"))   make_goal(fp, k, v, c);
     if (!strcmp(v[i], "target_teleporter"))        make_jump(fp, k, v, c);
     if (!strcmp(v[i], "target_position"))          make_targ(fp, k, v, c);
-    if (!strcmp(v[i], "worldspawn"))               make_body(fp, k, v, c, l0);
+    if (!strcmp(v[i], "worldspawn"))
+    {
+        read_dict_entries = 1;
+        make_body(fp, k, v, c, l0);
+    }
     if (!strcmp(v[i], "func_train"))               make_body(fp, k, v, c, l0);
     if (!strcmp(v[i], "misc_model"))               make_body(fp, k, v, c, l0);
 }
 
-static void read_map(struct s_file *fp, FILE *fin)
+static void read_map(struct s_file *fp, fs_file fin)
 {
     char k[MAXSTR];
     char v[MAXSTR];
@@ -1314,7 +1368,7 @@ static void clip_vert(struct s_file *fp,
 
     m_basis(M, fp->sv[si].n, fp->sv[sj].n, fp->sv[sk].n);
     m_xps(X, M);
-        
+
     if (m_inv(I, X))
     {
         m_vxfm(p, I, d);
@@ -1350,14 +1404,18 @@ static void clip_edge(struct s_file *fp,
     int i, j;
 
     for (i = 1; i < lp->vc; i++)
+    {
+        int vi = fp->iv[lp->v0 + i];
+
+        if (!on_side(fp->vv[vi].p, fp->sv + si) ||
+            !on_side(fp->vv[vi].p, fp->sv + sj))
+            continue;
+
         for (j = 0; j < i; j++)
         {
-            int vi = fp->iv[lp->v0 + i];
             int vj = fp->iv[lp->v0 + j];
 
-            if (on_side(fp->vv[vi].p, fp->sv + si) &&
-                on_side(fp->vv[vj].p, fp->sv + si) &&
-                on_side(fp->vv[vi].p, fp->sv + sj) &&
+            if (on_side(fp->vv[vj].p, fp->sv + si) &&
                 on_side(fp->vv[vj].p, fp->sv + sj))
             {
                 fp->ev[fp->ec].vi = vi;
@@ -1370,12 +1428,13 @@ static void clip_edge(struct s_file *fp,
                 lp->ec++;
             }
         }
+    }
 }
 
 /*
  * Find all verts that lie on  the given side of the lump.  Sort these
  * verts to  have a counter-clockwise winding about  the plane normal.
- * Create geoms to tessalate the resulting convex polygon.
+ * Create geoms to tessellate the resulting convex polygon.
  */
 static void clip_geom(struct s_file *fp,
                       struct s_lump *lp, int si)
@@ -1454,7 +1513,7 @@ static void clip_geom(struct s_file *fp,
 }
 
 /*
- * Iterate the sides of the lump, attemping to generate a new vert for
+ * Iterate the sides of the lump, attempting to generate a new vert for
  * each trio of planes, a new edge  for each pair of planes, and a new
  * set of geom for each visible plane.
  */
@@ -1486,7 +1545,7 @@ static void clip_lump(struct s_file *fp, struct s_lump *lp)
     lp->gc = 0;
 
     for (i = 0; i < lp->sc; i++)
-        if (fp->mv[plane_m[fp->iv[lp->s0 + i]]].d[3] > 0)
+        if (fp->mv[plane_m[fp->iv[lp->s0 + i]]].d[3] > 0.0f)
             clip_geom(fp, lp,
                       fp->iv[lp->s0 + i]);
 
@@ -1508,7 +1567,7 @@ static void clip_file(struct s_file *fp)
 /*
  * For each body element type,  determine if element 'p' is equivalent
  * to element  'q'.  This  is more than  a simple memory  compare.  It
- * effectively  snaps mtrls and  verts togather,  and may  reverse the
+ * effectively  snaps mtrls and  verts together,  and may  reverse the
  * winding of  an edge or a geom.   This is done in  order to maximize
  * the number of elements that can be eliminated.
  */
@@ -1612,6 +1671,30 @@ static void swap_mtrl(struct s_file *fp, int mi, int mj)
         if (fp->rv[i].mi == mi) fp->rv[i].mi = mj;
 }
 
+static int vert_swaps[MAXV];
+
+static void apply_vert_swaps(struct s_file *fp)
+{
+    int i, j;
+
+    for (i = 0; i < fp->ec; i++)
+    {
+        fp->ev[i].vi = vert_swaps[fp->ev[i].vi];
+        fp->ev[i].vj = vert_swaps[fp->ev[i].vj];
+    }
+
+    for (i = 0; i < fp->gc; i++)
+    {
+        fp->gv[i].vi = vert_swaps[fp->gv[i].vi];
+        fp->gv[i].vj = vert_swaps[fp->gv[i].vj];
+        fp->gv[i].vk = vert_swaps[fp->gv[i].vk];
+    }
+
+    for (i = 0; i < fp->lc; i++)
+        for (j = 0; j < fp->lv[i].vc; j++)
+            fp->iv[fp->lv[i].v0 + j] = vert_swaps[fp->iv[fp->lv[i].v0 + j]];
+}
+
 static void swap_vert(struct s_file *fp, int vi, int vj)
 {
     int i, j;
@@ -1635,61 +1718,64 @@ static void swap_vert(struct s_file *fp, int vi, int vj)
                 fp->iv[fp->lv[i].v0 + j]  = vj;
 }
 
-static void swap_edge(struct s_file *fp, int ei, int ej)
+static int edge_swaps[MAXE];
+
+static void apply_edge_swaps(struct s_file *fp)
 {
     int i, j;
 
     for (i = 0; i < fp->lc; i++)
         for (j = 0; j < fp->lv[i].ec; j++)
-            if (fp->iv[fp->lv[i].e0 + j] == ei)
-                fp->iv[fp->lv[i].e0 + j]  = ej;
+            fp->iv[fp->lv[i].e0 + j] = edge_swaps[fp->iv[fp->lv[i].e0 + j]];
 }
 
-static void swap_side(struct s_file *fp, int si, int sj)
+static int side_swaps[MAXS];
+
+static void apply_side_swaps(struct s_file *fp)
 {
     int i, j;
 
     for (i = 0; i < fp->gc; i++)
     {
-        if (fp->gv[i].si == si) fp->gv[i].si = sj;
-        if (fp->gv[i].sj == si) fp->gv[i].sj = sj;
-        if (fp->gv[i].sk == si) fp->gv[i].sk = sj;
+        fp->gv[i].si = side_swaps[fp->gv[i].si];
+        fp->gv[i].sj = side_swaps[fp->gv[i].sj];
+        fp->gv[i].sk = side_swaps[fp->gv[i].sk];
     }
     for (i = 0; i < fp->nc; i++)
-        if (fp->nv[i].si == si) fp->nv[i].si = sj;
+        fp->nv[i].si = side_swaps[fp->nv[i].si];
 
     for (i = 0; i < fp->lc; i++)
         for (j = 0; j < fp->lv[i].sc; j++)
-            if (fp->iv[fp->lv[i].s0 + j] == si)
-                fp->iv[fp->lv[i].s0 + j]  = sj;
+            fp->iv[fp->lv[i].s0 + j] = side_swaps[fp->iv[fp->lv[i].s0 + j]];
 }
 
-static void swap_texc(struct s_file *fp, int ti, int tj)
+static int texc_swaps[MAXT];
+
+static void apply_texc_swaps(struct s_file *fp)
 {
     int i;
 
     for (i = 0; i < fp->gc; i++)
     {
-        if (fp->gv[i].ti == ti) fp->gv[i].ti = tj;
-        if (fp->gv[i].tj == ti) fp->gv[i].tj = tj;
-        if (fp->gv[i].tk == ti) fp->gv[i].tk = tj;
+        fp->gv[i].ti = texc_swaps[fp->gv[i].ti];
+        fp->gv[i].tj = texc_swaps[fp->gv[i].tj];
+        fp->gv[i].tk = texc_swaps[fp->gv[i].tk];
     }
 }
 
+static int geom_swaps[MAXG];
 
-static void swap_geom(struct s_file *fp, int gi, int gj)
+static void apply_geom_swaps(struct s_file *fp)
 {
     int i, j;
 
     for (i = 0; i < fp->lc; i++)
         for (j = 0; j < fp->lv[i].gc; j++)
-            if (fp->iv[fp->lv[i].g0 + j] == gi)
-                fp->iv[fp->lv[i].g0 + j]  = gj;
+            fp->iv[fp->lv[i].g0 + j] = geom_swaps[fp->iv[fp->lv[i].g0 + j]];
 
     for (i = 0; i < fp->bc; i++)
         for (j = 0; j < fp->bv[i].gc; j++)
-            if (fp->iv[fp->bv[i].g0 + j] == gi)
-                fp->iv[fp->bv[i].g0 + j]  = gj;
+            fp->iv[fp->bv[i].g0 + j] = geom_swaps[fp->iv[fp->bv[i].g0 + j]];
 }
 
 /*---------------------------------------------------------------------------*/
@@ -1729,22 +1815,20 @@ static void uniq_vert(struct s_file *fp)
     {
         for (j = 0; j < k; j++)
             if (comp_vert(fp->vv + i, fp->vv + j))
-            {
-                swap_vert(fp, i, j);
                 break;
-            }
+
+        vert_swaps[i] = j;
 
         if (j == k)
         {
             if (i != k)
-            {
                 fp->vv[k] = fp->vv[i];
-                swap_vert(fp, i, k);
-            }
             k++;
         }
     }
 
+    apply_vert_swaps(fp);
+
     fp->vc = k;
 }
 
@@ -1756,49 +1840,55 @@ static void uniq_edge(struct s_file *fp)
     {
         for (j = 0; j < k; j++)
             if (comp_edge(fp->ev + i, fp->ev + j))
-            {
-                swap_edge(fp, i, j);
                 break;
-            }
+
+        edge_swaps[i] = j;
 
         if (j == k)
         {
             if (i != k)
-            {
                 fp->ev[k] = fp->ev[i];
-                swap_edge(fp, i, k);
-            }
             k++;
         }
     }
 
+    apply_edge_swaps(fp);
+
     fp->ec = k;
 }
 
+static int geomlist[MAXV];
+static int nextgeom[MAXG];
+
 static void uniq_geom(struct s_file *fp)
 {
     int i, j, k = 0;
 
+    for (i = 0; i < MAXV; i++)
+        geomlist[i] = -1;
+
     for (i = 0; i < fp->gc; i++)
     {
-        for (j = 0; j < k; j++)
+        int key = fp->gv[i].vj;
+
+        for (j = geomlist[key]; j != -1; j = nextgeom[j])
             if (comp_geom(fp->gv + i, fp->gv + j))
-            {
-                swap_geom(fp, i, j);
-                break;
-            }
+                goto found;
 
-        if (j == k)
-        {
-            if (i != k)
-            {
-                fp->gv[k] = fp->gv[i];
-                swap_geom(fp, i, k);
-            }
-            k++;
-        }
+        fp->gv[k] = fp->gv[i];
+
+        nextgeom[k] = geomlist[key];
+        geomlist[key] = k;
+
+        j = k;
+        k++;
+
+found:
+        geom_swaps[i] = j;
     }
 
+    apply_geom_swaps(fp);
+
     fp->gc = k;
 }
 
@@ -1810,22 +1900,20 @@ static void uniq_texc(struct s_file *fp)
     {
         for (j = 0; j < k; j++)
             if (comp_texc(fp->tv + i, fp->tv + j))
-            {
-                swap_texc(fp, i, j);
                 break;
-            }
+
+        texc_swaps[i] = j;
 
         if (j == k)
         {
             if (i != k)
-            {
                 fp->tv[k] = fp->tv[i];
-                swap_texc(fp, i, k);
-            }
             k++;
         }
     }
 
+    apply_texc_swaps(fp);
+
     fp->tc = k;
 }
 
@@ -1837,46 +1925,201 @@ static void uniq_side(struct s_file *fp)
     {
         for (j = 0; j < k; j++)
             if (comp_side(fp->sv + i, fp->sv + j))
-            {
-                swap_side(fp, i, j);
                 break;
-            }
+
+        side_swaps[i] = j;
 
         if (j == k)
         {
             if (i != k)
-            {
                 fp->sv[k] = fp->sv[i];
-                swap_side(fp, i, k);
-            }
             k++;
         }
     }
 
+    apply_side_swaps(fp);
+
     fp->sc = k;
 }
 
 static void uniq_file(struct s_file *fp)
 {
-    uniq_mtrl(fp);
-    uniq_vert(fp);
-    uniq_edge(fp);
-    uniq_side(fp);
-    uniq_texc(fp);
-    uniq_geom(fp);
+    /* Debug mode skips optimization, producing oversized output files. */
+
+    if (debug_output == 0)
+    {
+        uniq_mtrl(fp);
+        uniq_vert(fp);
+        uniq_edge(fp);
+        uniq_side(fp);
+        uniq_texc(fp);
+        uniq_geom(fp);
+    }
+}
+
+/*---------------------------------------------------------------------------*/
+
+struct s_trip
+{
+    int vi;
+    int mi;
+    int si;
+    int gi;
+};
+
+static int comp_trip(const void *p, const void *q)
+{
+    const struct s_trip *tp = (const struct s_trip *) p;
+    const struct s_trip *tq = (const struct s_trip *) q;
+
+    if (tp->vi < tq->vi) return -1;
+    if (tp->vi > tq->vi) return +1;
+    if (tp->mi < tq->mi) return -1;
+    if (tp->mi > tq->mi) return +1;
+
+    return 0;
+}
+
+static void smth_file(struct s_file *fp)
+{
+    struct s_trip temp, *T;
+
+    if (debug_output == 0)
+    {
+        if ((T = (struct s_trip *) malloc(fp->gc * 3 * sizeof (struct s_trip))))
+        {
+            int gi, i, j, k, l, c = 0;
+
+            /* Create a list of all non-faceted vertex triplets. */
+
+            for (gi = 0; gi < fp->gc; ++gi)
+            {
+                struct s_geom *gp = fp->gv + gi;
+
+                T[c].vi = gp->vi;
+                T[c].mi = gp->mi;
+                T[c].si = gp->si;
+                T[c].gi = gi;
+                c++;
+
+                T[c].vi = gp->vj;
+                T[c].mi = gp->mi;
+                T[c].si = gp->sj;
+                T[c].gi = gi;
+                c++;
+
+                T[c].vi = gp->vk;
+                T[c].mi = gp->mi;
+                T[c].si = gp->sk;
+                T[c].gi = gi;
+                c++;
+            }
+
+            /* Sort all triplets by vertex index and material. */
+
+            qsort(T, c, sizeof (struct s_trip), comp_trip);
+
+            /* For each set of triplets sharing vertex index and material... */
+
+            for (i = 0; i < c; i = l)
+            {
+                int acc = 0;
+
+                float N[3], angle = fp->mv[T[i].mi].angle;
+                const float   *Ni = fp->sv[T[i].si].n;
+
+                /* Sort the set by side similarity to the first. */
+
+                for (j = i + 1; j < c && (T[j].vi == T[i].vi &&
+                                          T[j].mi == T[i].mi); ++j)
+                {
+                    for (k = j + 1; k < c && (T[k].vi == T[i].vi &&
+                                              T[k].mi == T[i].mi); ++k)
+                    {
+                        const float *Nj = fp->sv[T[j].si].n;
+                        const float *Nk = fp->sv[T[k].si].n;
+
+                        if (T[j].si != T[k].si && v_dot(Nk, Ni) > v_dot(Nj, Ni))
+                        {
+                            temp = T[k];
+                            T[k] = T[j];
+                            T[j] = temp;
+                        }
+                    }
+                }
+
+                /* Accumulate all similar side normals. */
+
+                N[0] = Ni[0];
+                N[1] = Ni[1];
+                N[2] = Ni[2];
+
+                for (l = i + 1; l < c && (T[l].vi == T[i].vi &&
+                                          T[l].mi == T[i].mi); ++l)
+                    if (T[l].si != T[i].si)
+                    {
+                        const float *Nl = fp->sv[T[l].si].n;
+
+                        if (V_DEG(facosf(v_dot(Ni, Nl))) > angle)
+                            break;
+
+                        N[0] += Nl[0];
+                        N[1] += Nl[1];
+                        N[2] += Nl[2];
+
+                        acc++;
+                    }
+
+                /* If at least two normals have been accumulated... */
+
+                if (acc)
+                {
+                    /* Store the accumulated normal as a new side. */
+
+                    int ss = incs(fp);
+
+                    v_nrm(fp->sv[ss].n, N);
+                    fp->sv[ss].d = 0.0f;
+
+                    /* Assign the new normal to the merged triplets. */
+
+                    for (j = i; j < l; ++j)
+                        T[j].si = ss;
+                }
+            }
+
+            /* Assign the remapped normals to the original geoms. */
+
+            for (i = 0; i < c; ++i)
+            {
+                struct s_geom *gp = fp->gv + T[i].gi;
+
+                if (gp->vi == T[i].vi) gp->si = T[i].si;
+                if (gp->vj == T[i].vi) gp->sj = T[i].si;
+                if (gp->vk == T[i].vi) gp->sk = T[i].si;
+            }
+
+            free(T);
+        }
+
+        uniq_side(fp);
+    }
 }
 
+
 /*---------------------------------------------------------------------------*/
 
 static void sort_file(struct s_file *fp)
 {
     int i, j;
 
-    /* Sort billboards farthest to nearest. */
+    /* Sort billboards by material within distance. */
 
     for (i = 0; i < fp->rc; i++)
         for (j = i + 1; j < fp->rc; j++)
-            if (fp->rv[j].d > fp->rv[i].d)
+            if ((fp->rv[j].d  > fp->rv[i].d) ||
+                (fp->rv[j].d == fp->rv[i].d &&
+                 fp->rv[j].mi > fp->rv[i].mi))
             {
                 struct s_bill t;
 
@@ -1906,7 +2149,8 @@ static void sort_file(struct s_file *fp)
 
 static int test_lump_side(const struct s_file *fp,
                           const struct s_lump *lp,
-                          const struct s_side *sp)
+                          const struct s_side *sp,
+                          float bsphere[4])
 {
     int si;
     int vi;
@@ -1914,6 +2158,18 @@ static int test_lump_side(const struct s_file *fp,
     int f = 0;
     int b = 0;
 
+    float d;
+
+    if (!lp->vc)
+        return 0;
+
+    /* Check if the bounding sphere of the lump is completely on one side. */
+
+    d = v_dot(bsphere, sp->n) - sp->d;
+
+    if (fabs(d) > bsphere[3])
+        return d > 0 ? 1 : -1;
+
     /* If the given side is part of the given lump, then the lump is behind. */
 
     for (si = 0; si < lp->sc; si++)
@@ -1940,7 +2196,7 @@ static int test_lump_side(const struct s_file *fp,
     return 0;
 }
 
-static int node_node(struct s_file *fp, int l0, int lc)
+static int node_node(struct s_file *fp, int l0, int lc, float bsphere[][4])
 {
     if (lc < 8)
     {
@@ -1974,7 +2230,10 @@ static int node_node(struct s_file *fp, int l0, int lc)
             int k = 0;
 
             for (li = 0; li < lc; li++)
-                if ((k = test_lump_side(fp, fp->lv + l0 + li, fp->sv + si)))
+                if ((k = test_lump_side(fp,
+                                        fp->lv + l0 + li,
+                                        fp->sv + si,
+                                        bsphere[l0 + li])))
                     d += k;
                 else
                     o++;
@@ -1992,20 +2251,47 @@ static int node_node(struct s_file *fp, int l0, int lc)
         /* Flag each lump with its position WRT the side. */
 
         for (li = 0; li < lc; li++)
-            switch (test_lump_side(fp, fp->lv + l0 + li, fp->sv + sj))
+            if (debug_output)
             {
-            case +1: fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x10; break;
-            case  0: fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x20; break;
-            case -1: fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x40; break;
+                fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x20;
+            }
+            else
+            {
+                switch (test_lump_side(fp,
+                                       fp->lv + l0 + li,
+                                       fp->sv + sj,
+                                       bsphere[l0 + li]))
+                {
+                case +1:
+                    fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x10;
+                    break;
+
+                case  0:
+                    fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x20;
+                    break;
+
+                case -1:
+                    fp->lv[l0+li].fl = (fp->lv[l0+li].fl & 1) | 0x40;
+                    break;
+                }
             }
 
         /* Sort all lumps in the range by their flag values. */
-        
+
         for (li = 1; li < lc; li++)
             for (lj = 0; lj < li; lj++)
                 if (fp->lv[l0 + li].fl < fp->lv[l0 + lj].fl)
                 {
                     struct s_lump l;
+                    float f;
+                    int i;
+
+                    for (i = 0; i < 4; i++)
+                    {
+                        f                   = bsphere[l0 + li][i];
+                        bsphere[l0 + li][i] = bsphere[l0 + lj][i];
+                        bsphere[l0 + lj][i] =                   f;
+                    }
 
                     l               = fp->lv[l0 + li];
                     fp->lv[l0 + li] = fp->lv[l0 + lj];
@@ -2031,9 +2317,9 @@ static int node_node(struct s_file *fp, int l0, int lc)
         i = incn(fp);
 
         fp->nv[i].si = sj;
-        fp->nv[i].ni = node_node(fp, li, lic);
+        fp->nv[i].ni = node_node(fp, li, lic, bsphere);
 
-        fp->nv[i].nj = node_node(fp, lk, lkc);
+        fp->nv[i].nj = node_node(fp, lk, lkc, bsphere);
         fp->nv[i].l0 = lj;
         fp->nv[i].lc = ljc;
 
@@ -2041,24 +2327,83 @@ static int node_node(struct s_file *fp, int l0, int lc)
     }
 }
 
+/*
+ * Compute a bounding sphere for a lump (not optimal)
+ */
+static void lump_bounding_sphere(struct s_file *fp,
+                                 struct s_lump *lp,
+                                 float bsphere[4])
+{
+    float bbox[6];
+    float r;
+    int i;
+
+    if (!lp->vc)
+        return;
+
+    bbox[0] = bbox[3] = fp->vv[fp->iv[lp->v0]].p[0];
+    bbox[1] = bbox[4] = fp->vv[fp->iv[lp->v0]].p[1];
+    bbox[2] = bbox[5] = fp->vv[fp->iv[lp->v0]].p[2];
+
+    for (i = 1; i < lp->vc; i++)
+    {
+        struct s_vert *vp = fp->vv + fp->iv[lp->v0 + i];
+        int j;
+
+        for (j = 0; j < 3; j++)
+            if (vp->p[j] < bbox[j])
+                bbox[j] = vp->p[j];
+
+        for (j = 0; j < 3; j++)
+            if (vp->p[j] > bbox[j + 3])
+                bbox[j + 3] = vp->p[j];
+    }
+
+    r = 0;
+
+    for (i = 0; i < 3; i++)
+    {
+        bsphere[i] = (bbox[i] + bbox[i + 3]) / 2;
+        r += (bsphere[i] - bbox[i]) * (bsphere[i] - bbox[i]);
+    }
+
+    bsphere[3] = fsqrtf(r);
+}
+
 static void node_file(struct s_file *fp)
 {
-    int bi;
+    float bsphere[MAXL][4];
+    int i;
+
+    /* Compute a bounding sphere for each lump. */
+
+    for (i = 0; i < fp->lc; i++)
+        lump_bounding_sphere(fp, fp->lv + i, bsphere[i]);
 
     /* Sort the lumps of each body into BSP nodes. */
 
-    for (bi = 0; bi < fp->bc; bi++)
-        fp->bv[bi].ni = node_node(fp, fp->bv[bi].l0, fp->bv[bi].lc);
+    for (i = 0; i < fp->bc; i++)
+        fp->bv[i].ni = node_node(fp, fp->bv[i].l0, fp->bv[i].lc, bsphere);
 }
 
 /*---------------------------------------------------------------------------*/
 
 static void dump_file(struct s_file *p, const char *name)
 {
+    /* FIXME:  Count visible geoms.
+     *
+     * I'm afraid items break this (not sure though) so leaving it out.
+     */
+
+#if 0
     int i, j;
+#endif
+    int i;
     int c = 0;
     int n = 0;
+#if 0
     int m = p->rc + p->cc * 128 + (p->zc * p->jc + p->xc) * 32;
+#endif
 
     /* Count the number of solid lumps. */
 
@@ -2066,6 +2411,7 @@ static void dump_file(struct s_file *p, const char *name)
         if ((p->lv[i].fl & 1) == 0)
             n++;
 
+#if 0
     /* Count the number of visible geoms. */
 
     for (i = 0; i < p->bc; i++)
@@ -2074,75 +2420,97 @@ static void dump_file(struct s_file *p, const char *name)
             m += p->lv[p->bv[i].l0 + j].gc;
         m += p->bv[i].gc;
     }
+#endif
 
     /* Count the total value of all coins. */
 
-    for (i = 0; i < p->cc; i++)
-        c += p->cv[i].n;
+    for (i = 0; i < p->hc; i++)
+        if (p->hv[i].t == ITEM_COIN)
+            c += p->hv[i].n;
 
+#if 0
     printf("%s (%d/%d/$%d)\n"
+#endif
+    printf("%s (%d/$%d)\n"
            "  mtrl  vert  edge  side  texc"
            "  geom  lump  path  node  body\n"
            "%6d%6d%6d%6d%6d%6d%6d%6d%6d%6d\n"
-           "  coin  goal  view  jump  swch"
-           "  bill  ball  char  indx\n"
-           "%6d%6d%6d%6d%6d%6d%6d%6d%6d\n",
+           "  item  goal  view  jump  swch"
+           "  bill  ball  char  dict  indx\n"
+           "%6d%6d%6d%6d%6d%6d%6d%6d%6d%6d\n",
+#if 0
            name, n, m, c,
+#endif
+           name, n, c,
            p->mc, p->vc, p->ec, p->sc, p->tc,
            p->gc, p->lc, p->pc, p->nc, p->bc,
-           p->cc, p->zc, p->wc, p->jc, p->xc,
-           p->rc, p->uc, p->ac, p->ic);
+           p->hc, p->zc, p->wc, p->jc, p->xc,
+           p->rc, p->uc, p->ac, p->dc, p->ic);
 }
 
-/* Skip the ugly SDL main substitution since we only need sdl_image. */
-#ifdef main
-#    undef main
-#endif
-
 int main(int argc, char *argv[])
 {
-    char src[MAXSTR];
-    char dst[MAXSTR];
+    char src[MAXSTR] = "";
+    char dst[MAXSTR] = "";
     struct s_file f;
-    FILE *fin;
+    fs_file fin;
+
+    if (!fs_init(argv[0]))
+    {
+        fprintf(stderr, "Failure to initialize virtual file system\n");
+        return 1;
+    }
 
     if (argc > 2)
     {
-        if (config_data_path(argv[2], NULL))
-        {
-            strncpy(src,  argv[1], MAXSTR);
-            strncpy(dst,  argv[1], MAXSTR);
+        input_file = argv[1];
 
-            if (strcmp(dst + strlen(dst) - 4, ".map") == 0)
-                strcpy(dst + strlen(dst) - 4, ".sol");
-            else
-                strcat(dst, ".sol");
+        if (argc > 3 && strcmp(argv[3], "--debug") == 0)
+            debug_output = 1;
 
-            if ((fin = fopen(src, "r")))
+        strncpy(src, argv[1], MAXSTR - 1);
+        strncpy(dst, argv[1], MAXSTR - 1);
+
+        if (strcmp(dst + strlen(dst) - 4, ".map") == 0)
+            strcpy(dst + strlen(dst) - 4, ".sol");
+        else
+            strcat(dst, ".sol");
+
+        fs_add_path     (dir_name(src));
+        fs_set_write_dir(dir_name(dst));
+
+        if ((fin = fs_open(base_name(src, NULL), "r")))
+        {
+            if (!fs_add_path_with_archives(argv[2]))
             {
-                init_file(&f);
-                read_map(&f, fin);
+                fprintf(stderr, "Failure to establish data directory\n");
+                fs_close(fin);
+                fs_quit();
+                return 1;
+            }
 
-                resolve();
-                targets(&f);
+            init_file(&f);
+            read_map(&f, fin);
 
-                clip_file(&f);
-                move_file(&f);
-                uniq_file(&f);
-                sort_file(&f);
-                node_file(&f);
-                dump_file(&f, dst);
+            resolve();
+            targets(&f);
 
-                sol_stor(&f, dst);
+            clip_file(&f);
+            move_file(&f);
+            uniq_file(&f);
+            smth_file(&f);
+            sort_file(&f);
+            node_file(&f);
+            dump_file(&f, dst);
 
-                fclose(fin);
+            sol_stor(&f, base_name(dst, NULL));
 
-                free_imagedata();
-            }
+            fs_close(fin);
+
+            free_imagedata();
         }
-        else fprintf(stderr, "Failure to establish data directory\n");
     }
-    else fprintf(stderr, "Usage: %s <map> [data]\n", argv[0]);
+    else fprintf(stderr, "Usage: %s <map> <data> [--debug]\n", argv[0]);
 
     return 0;
 }