]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_brush.c
collision brush generation in q3bsp is now *MUCH* faster and uses a lot less memory...
[xonotic/darkplaces.git] / model_brush.c
index 5530052b76dbd0b3fcf38469d1d9ffa025c646b8..19aaf6aa9386f5023c861bebfee56baa4c9ea607 100644 (file)
@@ -21,6 +21,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "quakedef.h"
 #include "image.h"
 #include "r_shadow.h"
+#include "winding.h"
+#include "curves.h"
 
 // note: model_shared.c sets up r_notexture, and r_surf_notexture
 
@@ -32,7 +34,9 @@ cvar_t r_novis = {0, "r_novis", "0"};
 cvar_t r_miplightmaps = {CVAR_SAVE, "r_miplightmaps", "0"};
 cvar_t r_lightmaprgba = {0, "r_lightmaprgba", "1"};
 cvar_t r_nosurftextures = {0, "r_nosurftextures", "0"};
-cvar_t r_sortsurfaces = {0, "r_sortsurfaces", "0"};
+cvar_t mod_q3bsp_curves_subdivide_level = {0, "mod_q3bsp_curves_subdivide_level", "2"};
+cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1"};
+cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1"};
 
 void Mod_BrushInit(void)
 {
@@ -42,7 +46,9 @@ void Mod_BrushInit(void)
        Cvar_RegisterVariable(&r_miplightmaps);
        Cvar_RegisterVariable(&r_lightmaprgba);
        Cvar_RegisterVariable(&r_nosurftextures);
-       Cvar_RegisterVariable(&r_sortsurfaces);
+       Cvar_RegisterVariable(&mod_q3bsp_curves_subdivide_level);
+       Cvar_RegisterVariable(&mod_q3bsp_curves_collisions);
+       Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline);
        memset(mod_q1bsp_novis, 0xff, sizeof(mod_q1bsp_novis));
 }
 
@@ -163,7 +169,6 @@ static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *in
        float surfnormal[3];
 #endif
        msurface_t *surf;
-       surfmesh_t *mesh;
        for (surfnum = 0, mark = leaf->firstmarksurface;surfnum < leaf->nummarksurfaces;surfnum++, mark++)
        {
                surf = info->model->brushq1.surfaces + *mark;
@@ -174,103 +179,100 @@ static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *in
                        if (surf->flags & SURF_PLANEBACK)
                                VectorNegate(surfnormal, surfnormal);
 #endif
-                       for (mesh = surf->mesh;mesh;mesh = mesh->chain)
+                       for (k = 0;k < surf->mesh.num_triangles;k++)
                        {
-                               for (k = 0;k < mesh->numtriangles;k++)
+                               tri = surf->mesh.data_element3i + k * 3;
+                               VectorCopy((surf->mesh.data_vertex3f + tri[0] * 3), vert[0]);
+                               VectorCopy((surf->mesh.data_vertex3f + tri[1] * 3), vert[1]);
+                               VectorCopy((surf->mesh.data_vertex3f + tri[2] * 3), vert[2]);
+                               VectorSubtract(vert[1], vert[0], edge[0]);
+                               VectorSubtract(vert[2], vert[1], edge[1]);
+                               CrossProduct(edge[1], edge[0], facenormal);
+                               if (facenormal[0] || facenormal[1] || facenormal[2])
                                {
-                                       tri = mesh->element3i + k * 3;
-                                       VectorCopy((mesh->vertex3f + tri[0] * 3), vert[0]);
-                                       VectorCopy((mesh->vertex3f + tri[1] * 3), vert[1]);
-                                       VectorCopy((mesh->vertex3f + tri[2] * 3), vert[2]);
-                                       VectorSubtract(vert[1], vert[0], edge[0]);
-                                       VectorSubtract(vert[2], vert[1], edge[1]);
-                                       CrossProduct(edge[1], edge[0], facenormal);
-                                       if (facenormal[0] || facenormal[1] || facenormal[2])
+                                       VectorNormalize(facenormal);
+#if 0
+                                       if (VectorDistance(facenormal, surfnormal) > 0.01f)
+                                               Con_Printf("a2! %f %f %f != %f %f %f\n", facenormal[0], facenormal[1], facenormal[2], surfnormal[0], surfnormal[1], surfnormal[2]);
+#endif
+                                       f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+                                       if (f <= info->bestdist && f >= -info->bestdist)
                                        {
-                                               VectorNormalize(facenormal);
+                                               VectorSubtract(vert[0], vert[2], edge[2]);
+                                               VectorNormalize(edge[0]);
+                                               VectorNormalize(edge[1]);
+                                               VectorNormalize(edge[2]);
+                                               CrossProduct(facenormal, edge[0], edgenormal[0]);
+                                               CrossProduct(facenormal, edge[1], edgenormal[1]);
+                                               CrossProduct(facenormal, edge[2], edgenormal[2]);
 #if 0
-                                               if (VectorDistance(facenormal, surfnormal) > 0.01f)
-                                                       Con_Printf("a2! %f %f %f != %f %f %f\n", facenormal[0], facenormal[1], facenormal[2], surfnormal[0], surfnormal[1], surfnormal[2]);
+                                               if (samelevel.integer & 1)
+                                                       VectorNegate(edgenormal[0], edgenormal[0]);
+                                               if (samelevel.integer & 2)
+                                                       VectorNegate(edgenormal[1], edgenormal[1]);
+                                               if (samelevel.integer & 4)
+                                                       VectorNegate(edgenormal[2], edgenormal[2]);
+                                               for (i = 0;i < 3;i++)
+                                                       if (DotProduct(vert[0], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f
+                                                        || DotProduct(vert[1], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f
+                                                        || DotProduct(vert[2], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f)
+                                                               Con_Printf("a! %i : %f %f %f (%f %f %f)\n", i, edgenormal[i][0], edgenormal[i][1], edgenormal[i][2], facenormal[0], facenormal[1], facenormal[2]);
 #endif
-                                               f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
-                                               if (f <= info->bestdist && f >= -info->bestdist)
+                                               // face distance
+                                               if (DotProduct(info->center, edgenormal[0]) < DotProduct(vert[0], edgenormal[0])
+                                                && DotProduct(info->center, edgenormal[1]) < DotProduct(vert[1], edgenormal[1])
+                                                && DotProduct(info->center, edgenormal[2]) < DotProduct(vert[2], edgenormal[2]))
                                                {
-                                                       VectorSubtract(vert[0], vert[2], edge[2]);
-                                                       VectorNormalize(edge[0]);
-                                                       VectorNormalize(edge[1]);
-                                                       VectorNormalize(edge[2]);
-                                                       CrossProduct(facenormal, edge[0], edgenormal[0]);
-                                                       CrossProduct(facenormal, edge[1], edgenormal[1]);
-                                                       CrossProduct(facenormal, edge[2], edgenormal[2]);
-#if 0
-                                                       if (samelevel.integer & 1)
-                                                               VectorNegate(edgenormal[0], edgenormal[0]);
-                                                       if (samelevel.integer & 2)
-                                                               VectorNegate(edgenormal[1], edgenormal[1]);
-                                                       if (samelevel.integer & 4)
-                                                               VectorNegate(edgenormal[2], edgenormal[2]);
+                                                       // we got lucky, the center is within the face
+                                                       dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+                                                       if (dist < 0)
+                                                       {
+                                                               dist = -dist;
+                                                               if (info->bestdist > dist)
+                                                               {
+                                                                       info->bestdist = dist;
+                                                                       VectorScale(facenormal, (info->radius - -dist), info->nudge);
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               if (info->bestdist > dist)
+                                                               {
+                                                                       info->bestdist = dist;
+                                                                       VectorScale(facenormal, (info->radius - dist), info->nudge);
+                                                               }
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       // check which edge or vertex the center is nearest
                                                        for (i = 0;i < 3;i++)
-                                                               if (DotProduct(vert[0], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f
-                                                                || DotProduct(vert[1], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f
-                                                                || DotProduct(vert[2], edgenormal[i]) > DotProduct(vert[i], edgenormal[i]) + 0.1f)
-                                                                       Con_Printf("a! %i : %f %f %f (%f %f %f)\n", i, edgenormal[i][0], edgenormal[i][1], edgenormal[i][2], facenormal[0], facenormal[1], facenormal[2]);
-#endif
-                                                       // face distance
-                                                       if (DotProduct(info->center, edgenormal[0]) < DotProduct(vert[0], edgenormal[0])
-                                                        && DotProduct(info->center, edgenormal[1]) < DotProduct(vert[1], edgenormal[1])
-                                                        && DotProduct(info->center, edgenormal[2]) < DotProduct(vert[2], edgenormal[2]))
                                                        {
-                                                               // we got lucky, the center is within the face
-                                                               dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
-                                                               if (dist < 0)
+                                                               f = DotProduct(info->center, edge[i]);
+                                                               if (f >= DotProduct(vert[0], edge[i])
+                                                                && f <= DotProduct(vert[1], edge[i]))
                                                                {
-                                                                       dist = -dist;
+                                                                       // on edge
+                                                                       VectorMA(info->center, -f, edge[i], point);
+                                                                       dist = sqrt(DotProduct(point, point));
                                                                        if (info->bestdist > dist)
                                                                        {
                                                                                info->bestdist = dist;
-                                                                               VectorScale(facenormal, (info->radius - -dist), info->nudge);
+                                                                               VectorScale(point, (info->radius / dist), info->nudge);
                                                                        }
+                                                                       // skip both vertex checks
+                                                                       // (both are further away than this edge)
+                                                                       i++;
                                                                }
                                                                else
                                                                {
+                                                                       // not on edge, check first vertex of edge
+                                                                       VectorSubtract(info->center, vert[i], point);
+                                                                       dist = sqrt(DotProduct(point, point));
                                                                        if (info->bestdist > dist)
                                                                        {
                                                                                info->bestdist = dist;
-                                                                               VectorScale(facenormal, (info->radius - dist), info->nudge);
-                                                                       }
-                                                               }
-                                                       }
-                                                       else
-                                                       {
-                                                               // check which edge or vertex the center is nearest
-                                                               for (i = 0;i < 3;i++)
-                                                               {
-                                                                       f = DotProduct(info->center, edge[i]);
-                                                                       if (f >= DotProduct(vert[0], edge[i])
-                                                                        && f <= DotProduct(vert[1], edge[i]))
-                                                                       {
-                                                                               // on edge
-                                                                               VectorMA(info->center, -f, edge[i], point);
-                                                                               dist = sqrt(DotProduct(point, point));
-                                                                               if (info->bestdist > dist)
-                                                                               {
-                                                                                       info->bestdist = dist;
-                                                                                       VectorScale(point, (info->radius / dist), info->nudge);
-                                                                               }
-                                                                               // skip both vertex checks
-                                                                               // (both are further away than this edge)
-                                                                               i++;
-                                                                       }
-                                                                       else
-                                                                       {
-                                                                               // not on edge, check first vertex of edge
-                                                                               VectorSubtract(info->center, vert[i], point);
-                                                                               dist = sqrt(DotProduct(point, point));
-                                                                               if (info->bestdist > dist)
-                                                                               {
-                                                                                       info->bestdist = dist;
-                                                                                       VectorScale(point, (info->radius / dist), info->nudge);
-                                                                               }
+                                                                               VectorScale(point, (info->radius / dist), info->nudge);
                                                                        }
                                                                }
                                                        }
@@ -323,6 +325,41 @@ static void Mod_Q1BSP_FindNonSolidLocation(model_t *model, const vec3_t in, vec3
        VectorCopy(info.center, out);
 }
 
+int Mod_Q1BSP_SuperContentsFromNativeContents(model_t *model, int nativecontents)
+{
+       switch(nativecontents)
+       {
+               case CONTENTS_EMPTY:
+                       return 0;
+               case CONTENTS_SOLID:
+                       return SUPERCONTENTS_SOLID;
+               case CONTENTS_WATER:
+                       return SUPERCONTENTS_WATER;
+               case CONTENTS_SLIME:
+                       return SUPERCONTENTS_SLIME;
+               case CONTENTS_LAVA:
+                       return SUPERCONTENTS_LAVA;
+               case CONTENTS_SKY:
+                       return SUPERCONTENTS_SKY;
+       }
+       return 0;
+}
+
+int Mod_Q1BSP_NativeContentsFromSuperContents(model_t *model, int supercontents)
+{
+       if (supercontents & SUPERCONTENTS_SOLID)
+               return CONTENTS_SOLID;
+       if (supercontents & SUPERCONTENTS_SKY)
+               return CONTENTS_SKY;
+       if (supercontents & SUPERCONTENTS_LAVA)
+               return CONTENTS_LAVA;
+       if (supercontents & SUPERCONTENTS_SLIME)
+               return CONTENTS_SLIME;
+       if (supercontents & SUPERCONTENTS_WATER)
+               return CONTENTS_WATER;
+       return CONTENTS_EMPTY;
+}
+
 typedef struct
 {
        // the hull we're tracing through
@@ -364,37 +401,26 @@ loc0:
        // check for empty
        if (num < 0)
        {
-               t->trace->endcontents = num;
-               if (t->trace->thiscontents)
+               num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num);
+               if (!t->trace->startfound)
                {
-                       if (num == t->trace->thiscontents)
-                               t->trace->allsolid = false;
-                       else
-                       {
-                               // if the first leaf is solid, set startsolid
-                               if (t->trace->allsolid)
-                                       t->trace->startsolid = true;
-                               return HULLCHECKSTATE_SOLID;
-                       }
-                       return HULLCHECKSTATE_EMPTY;
+                       t->trace->startfound = true;
+                       t->trace->startsupercontents |= num;
+               }
+               if (num & SUPERCONTENTS_LIQUIDSMASK)
+                       t->trace->inwater = true;
+               if (num == 0)
+                       t->trace->inopen = true;
+               if (num & t->trace->hitsupercontentsmask)
+               {
+                       // if the first leaf is solid, set startsolid
+                       if (t->trace->allsolid)
+                               t->trace->startsolid = true;
+                       return HULLCHECKSTATE_SOLID;
                }
                else
                {
-                       if (num != CONTENTS_SOLID)
-                       {
-                               t->trace->allsolid = false;
-                               if (num == CONTENTS_EMPTY)
-                                       t->trace->inopen = true;
-                               else
-                                       t->trace->inwater = true;
-                       }
-                       else
-                       {
-                               // if the first leaf is solid, set startsolid
-                               if (t->trace->allsolid)
-                                       t->trace->startsolid = true;
-                               return HULLCHECKSTATE_SOLID;
-                       }
+                       t->trace->allsolid = false;
                        return HULLCHECKSTATE_EMPTY;
                }
        }
@@ -483,7 +509,7 @@ loc0:
        return HULLCHECKSTATE_DONE;
 }
 
-static void Mod_Q1BSP_TraceBox(struct model_s *model, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs)
+static void Mod_Q1BSP_TraceBox(struct model_s *model, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs, int hitsupercontentsmask)
 {
        // this function currently only supports same size start and end
        double boxsize[3];
@@ -492,6 +518,7 @@ static void Mod_Q1BSP_TraceBox(struct model_s *model, trace_t *trace, const vec3
        memset(&rhc, 0, sizeof(rhc));
        memset(trace, 0, sizeof(trace_t));
        rhc.trace = trace;
+       rhc.trace->hitsupercontentsmask = hitsupercontentsmask;
        rhc.trace->fraction = 1;
        rhc.trace->allsolid = true;
        VectorSubtract(boxstartmaxs, boxstartmins, boxsize);
@@ -706,15 +733,19 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
 
        loadmodel->brushq1.textures = NULL;
 
-       if (!l->filelen)
-               return;
-
-       m = (dmiptexlump_t *)(mod_base + l->fileofs);
-
-       m->nummiptex = LittleLong (m->nummiptex);
-
        // add two slots for notexture walls and notexture liquids
-       loadmodel->brushq1.numtextures = m->nummiptex + 2;
+       if (l->filelen)
+       {
+               m = (dmiptexlump_t *)(mod_base + l->fileofs);
+               m->nummiptex = LittleLong (m->nummiptex);
+               loadmodel->brushq1.numtextures = m->nummiptex + 2;
+       }
+       else
+       {
+               m = NULL;
+               loadmodel->brushq1.numtextures = 2;
+       }
+
        loadmodel->brushq1.textures = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numtextures * sizeof(texture_t));
 
        // fill out all slots with notexture
@@ -735,6 +766,9 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                tx->currentframe = tx;
        }
 
+       if (!m)
+               return;
+
        // just to work around bounds checking when debugging with it (array index out of bounds error thing)
        dofs = m->dataofs;
        // LordHavoc: mostly rewritten map texture loader
@@ -1004,9 +1038,9 @@ static void Mod_Q1BSP_LoadLighting(lump_t *l)
        else // LordHavoc: bsp version 29 (normal white lighting)
        {
                // LordHavoc: hope is not lost yet, check for a .lit file to load
-               strcpy(litfilename, loadmodel->name);
-               FS_StripExtension(litfilename, litfilename);
-               strcat(litfilename, ".lit");
+               strlcpy (litfilename, loadmodel->name, sizeof (litfilename));
+               FS_StripExtension (litfilename, litfilename, sizeof (litfilename));
+               strlcat (litfilename, ".lit", sizeof (litfilename));
                data = (qbyte*) FS_LoadFile(litfilename, false);
                if (data)
                {
@@ -1059,9 +1093,9 @@ static void Mod_Q1BSP_LoadLightList(void)
        char lightsfilename[1024], *s, *t, *lightsstring;
        mlight_t *e;
 
-       strcpy(lightsfilename, loadmodel->name);
-       FS_StripExtension(lightsfilename, lightsfilename);
-       strcat(lightsfilename, ".lights");
+       strlcpy (lightsfilename, loadmodel->name, sizeof (lightsfilename));
+       FS_StripExtension (lightsfilename, lightsfilename, sizeof(lightsfilename));
+       strlcat (lightsfilename, ".lights", sizeof (lightsfilename));
        s = lightsstring = (char *) FS_LoadFile(lightsfilename, false);
        if (s)
        {
@@ -1454,13 +1488,13 @@ static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surf)
                Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?\n");
 
        surf->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t));
-       mesh->numverts = subdivpolyverts;
-       mesh->numtriangles = subdivpolytriangles;
+       mesh->num_vertices = subdivpolyverts;
+       mesh->num_triangles = subdivpolytriangles;
        mesh->vertex = (surfvertex_t *)(mesh + 1);
-       mesh->index = (int *)(mesh->vertex + mesh->numverts);
-       memset(mesh->vertex, 0, mesh->numverts * sizeof(surfvertex_t));
+       mesh->index = (int *)(mesh->vertex + mesh->num_vertices);
+       memset(mesh->vertex, 0, mesh->num_vertices * sizeof(surfvertex_t));
 
-       for (i = 0;i < mesh->numtriangles;i++)
+       for (i = 0;i < mesh->num_triangles;i++)
                for (j = 0;j < 3;j++)
                        mesh->index[i*3+j] = subdivpolyindex[i][j];
 
@@ -1477,18 +1511,18 @@ static surfmesh_t *Mod_Q1BSP_AllocSurfMesh(int numverts, int numtriangles)
 {
        surfmesh_t *mesh;
        mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + numtriangles * sizeof(int[6]) + numverts * (3 + 2 + 2 + 2 + 3 + 3 + 3 + 1) * sizeof(float));
-       mesh->numverts = numverts;
-       mesh->numtriangles = numtriangles;
-       mesh->vertex3f = (float *)(mesh + 1);
-       mesh->texcoordtexture2f = mesh->vertex3f + mesh->numverts * 3;
-       mesh->texcoordlightmap2f = mesh->texcoordtexture2f + mesh->numverts * 2;
-       mesh->texcoorddetail2f = mesh->texcoordlightmap2f + mesh->numverts * 2;
-       mesh->svector3f = (float *)(mesh->texcoorddetail2f + mesh->numverts * 2);
-       mesh->tvector3f = mesh->svector3f + mesh->numverts * 3;
-       mesh->normal3f = mesh->tvector3f + mesh->numverts * 3;
-       mesh->lightmapoffsets = (int *)(mesh->normal3f + mesh->numverts * 3);
-       mesh->element3i = mesh->lightmapoffsets + mesh->numverts;
-       mesh->neighbor3i = mesh->element3i + mesh->numtriangles * 3;
+       mesh->num_vertices = numverts;
+       mesh->num_triangles = numtriangles;
+       mesh->data_vertex3f = (float *)(mesh + 1);
+       mesh->data_texcoordtexture2f = mesh->data_vertex3f + mesh->num_vertices * 3;
+       mesh->data_texcoordlightmap2f = mesh->data_texcoordtexture2f + mesh->num_vertices * 2;
+       mesh->data_texcoorddetail2f = mesh->data_texcoordlightmap2f + mesh->num_vertices * 2;
+       mesh->data_svector3f = (float *)(mesh->data_texcoorddetail2f + mesh->num_vertices * 2);
+       mesh->data_tvector3f = mesh->data_svector3f + mesh->num_vertices * 3;
+       mesh->data_normal3f = mesh->data_tvector3f + mesh->num_vertices * 3;
+       mesh->data_lightmapoffsets = (int *)(mesh->data_normal3f + mesh->num_vertices * 3);
+       mesh->data_element3i = mesh->data_lightmapoffsets + mesh->num_vertices;
+       mesh->data_neighbor3i = mesh->data_element3i + mesh->num_triangles * 3;
        return mesh;
 }
 
@@ -1627,52 +1661,51 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
        }
 
        loadmodel->brushq1.entiremesh = Mod_Q1BSP_AllocSurfMesh(totalverts, totaltris);
-       loadmodel->brushq1.surfmeshes = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) * totalmeshes);
 
        for (surfnum = 0, surf = loadmodel->brushq1.surfaces, totalverts = 0, totaltris = 0, totalmeshes = 0;surfnum < count;surfnum++, totalverts += surf->poly_numverts, totaltris += surf->poly_numverts - 2, totalmeshes++, surf++)
        {
-               mesh = surf->mesh = loadmodel->brushq1.surfmeshes + totalmeshes;
-               mesh->numverts = surf->poly_numverts;
-               mesh->numtriangles = surf->poly_numverts - 2;
-               mesh->vertex3f = loadmodel->brushq1.entiremesh->vertex3f + totalverts * 3;
-               mesh->texcoordtexture2f = loadmodel->brushq1.entiremesh->texcoordtexture2f + totalverts * 2;
-               mesh->texcoordlightmap2f = loadmodel->brushq1.entiremesh->texcoordlightmap2f + totalverts * 2;
-               mesh->texcoorddetail2f = loadmodel->brushq1.entiremesh->texcoorddetail2f + totalverts * 2;
-               mesh->svector3f = loadmodel->brushq1.entiremesh->svector3f + totalverts * 3;
-               mesh->tvector3f = loadmodel->brushq1.entiremesh->tvector3f + totalverts * 3;
-               mesh->normal3f = loadmodel->brushq1.entiremesh->normal3f + totalverts * 3;
-               mesh->lightmapoffsets = loadmodel->brushq1.entiremesh->lightmapoffsets + totalverts;
-               mesh->element3i = loadmodel->brushq1.entiremesh->element3i + totaltris * 3;
-               mesh->neighbor3i = loadmodel->brushq1.entiremesh->neighbor3i + totaltris * 3;
+               mesh = &surf->mesh;
+               mesh->num_vertices = surf->poly_numverts;
+               mesh->num_triangles = surf->poly_numverts - 2;
+               mesh->data_vertex3f = loadmodel->brushq1.entiremesh->data_vertex3f + totalverts * 3;
+               mesh->data_texcoordtexture2f = loadmodel->brushq1.entiremesh->data_texcoordtexture2f + totalverts * 2;
+               mesh->data_texcoordlightmap2f = loadmodel->brushq1.entiremesh->data_texcoordlightmap2f + totalverts * 2;
+               mesh->data_texcoorddetail2f = loadmodel->brushq1.entiremesh->data_texcoorddetail2f + totalverts * 2;
+               mesh->data_svector3f = loadmodel->brushq1.entiremesh->data_svector3f + totalverts * 3;
+               mesh->data_tvector3f = loadmodel->brushq1.entiremesh->data_tvector3f + totalverts * 3;
+               mesh->data_normal3f = loadmodel->brushq1.entiremesh->data_normal3f + totalverts * 3;
+               mesh->data_lightmapoffsets = loadmodel->brushq1.entiremesh->data_lightmapoffsets + totalverts;
+               mesh->data_element3i = loadmodel->brushq1.entiremesh->data_element3i + totaltris * 3;
+               mesh->data_neighbor3i = loadmodel->brushq1.entiremesh->data_neighbor3i + totaltris * 3;
 
                surf->lightmaptexturestride = 0;
                surf->lightmaptexture = NULL;
 
-               for (i = 0;i < mesh->numverts;i++)
+               for (i = 0;i < mesh->num_vertices;i++)
                {
-                       mesh->vertex3f[i * 3 + 0] = surf->poly_verts[i * 3 + 0];
-                       mesh->vertex3f[i * 3 + 1] = surf->poly_verts[i * 3 + 1];
-                       mesh->vertex3f[i * 3 + 2] = surf->poly_verts[i * 3 + 2];
-                       s = DotProduct((mesh->vertex3f + i * 3), surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3];
-                       t = DotProduct((mesh->vertex3f + i * 3), surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3];
-                       mesh->texcoordtexture2f[i * 2 + 0] = s / surf->texinfo->texture->width;
-                       mesh->texcoordtexture2f[i * 2 + 1] = t / surf->texinfo->texture->height;
-                       mesh->texcoorddetail2f[i * 2 + 0] = s * (1.0f / 16.0f);
-                       mesh->texcoorddetail2f[i * 2 + 1] = t * (1.0f / 16.0f);
-                       mesh->texcoordlightmap2f[i * 2 + 0] = 0;
-                       mesh->texcoordlightmap2f[i * 2 + 1] = 0;
-                       mesh->lightmapoffsets[i] = 0;
+                       mesh->data_vertex3f[i * 3 + 0] = surf->poly_verts[i * 3 + 0];
+                       mesh->data_vertex3f[i * 3 + 1] = surf->poly_verts[i * 3 + 1];
+                       mesh->data_vertex3f[i * 3 + 2] = surf->poly_verts[i * 3 + 2];
+                       s = DotProduct((mesh->data_vertex3f + i * 3), surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3];
+                       t = DotProduct((mesh->data_vertex3f + i * 3), surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3];
+                       mesh->data_texcoordtexture2f[i * 2 + 0] = s / surf->texinfo->texture->width;
+                       mesh->data_texcoordtexture2f[i * 2 + 1] = t / surf->texinfo->texture->height;
+                       mesh->data_texcoorddetail2f[i * 2 + 0] = s * (1.0f / 16.0f);
+                       mesh->data_texcoorddetail2f[i * 2 + 1] = t * (1.0f / 16.0f);
+                       mesh->data_texcoordlightmap2f[i * 2 + 0] = 0;
+                       mesh->data_texcoordlightmap2f[i * 2 + 1] = 0;
+                       mesh->data_lightmapoffsets[i] = 0;
                }
 
-               for (i = 0;i < mesh->numtriangles;i++)
+               for (i = 0;i < mesh->num_triangles;i++)
                {
-                       mesh->element3i[i * 3 + 0] = 0;
-                       mesh->element3i[i * 3 + 1] = i + 1;
-                       mesh->element3i[i * 3 + 2] = i + 2;
+                       mesh->data_element3i[i * 3 + 0] = 0;
+                       mesh->data_element3i[i * 3 + 1] = i + 1;
+                       mesh->data_element3i[i * 3 + 2] = i + 2;
                }
 
-               Mod_BuildTriangleNeighbors(mesh->neighbor3i, mesh->element3i, mesh->numtriangles);
-               Mod_BuildTextureVectorsAndNormals(mesh->numverts, mesh->numtriangles, mesh->vertex3f, mesh->texcoordtexture2f, mesh->element3i, mesh->svector3f, mesh->tvector3f, mesh->normal3f);
+               Mod_BuildTriangleNeighbors(mesh->data_neighbor3i, mesh->data_element3i, mesh->num_triangles);
+               Mod_BuildTextureVectorsAndNormals(mesh->num_vertices, mesh->num_triangles, mesh->data_vertex3f, mesh->data_texcoordtexture2f, mesh->data_element3i, mesh->data_svector3f, mesh->data_tvector3f, mesh->data_normal3f);
 
                if (surf->texinfo->texture->shader == &Cshader_wall_lightmap)
                {
@@ -1697,16 +1730,16 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                        uscale = (uscale - ubase) / (smax + 1);
                        vscale = (vscale - vbase) / (tmax + 1);
 
-                       for (i = 0;i < mesh->numverts;i++)
+                       for (i = 0;i < mesh->num_vertices;i++)
                        {
-                               u = ((DotProduct((mesh->vertex3f + i * 3), surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3]) + 8 - surf->texturemins[0]) * (1.0 / 16.0);
-                               v = ((DotProduct((mesh->vertex3f + i * 3), surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3]) + 8 - surf->texturemins[1]) * (1.0 / 16.0);
-                               mesh->texcoordlightmap2f[i * 2 + 0] = u * uscale + ubase;
-                               mesh->texcoordlightmap2f[i * 2 + 1] = v * vscale + vbase;
+                               u = ((DotProduct((mesh->data_vertex3f + i * 3), surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3]) + 8 - surf->texturemins[0]) * (1.0 / 16.0);
+                               v = ((DotProduct((mesh->data_vertex3f + i * 3), surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3]) + 8 - surf->texturemins[1]) * (1.0 / 16.0);
+                               mesh->data_texcoordlightmap2f[i * 2 + 0] = u * uscale + ubase;
+                               mesh->data_texcoordlightmap2f[i * 2 + 1] = v * vscale + vbase;
                                // LordHavoc: calc lightmap data offset for vertex lighting to use
                                iu = (int) u;
                                iv = (int) v;
-                               mesh->lightmapoffsets[i] = (bound(0, iv, tmax) * (smax+1) + bound(0, iu, smax)) * 3;
+                               mesh->data_lightmapoffsets[i] = (bound(0, iv, tmax) * (smax+1) + bound(0, iu, smax)) * 3;
                        }
                }
        }
@@ -1795,15 +1828,25 @@ static void Mod_Q1BSP_LoadLeafs(lump_t *l)
 
                out->firstmarksurface = loadmodel->brushq1.marksurfaces + LittleShort(in->firstmarksurface);
                out->nummarksurfaces = LittleShort(in->nummarksurfaces);
+               if (out->firstmarksurface < 0 || LittleShort(in->firstmarksurface) + out->nummarksurfaces > loadmodel->brushq1.nummarksurfaces)
+               {
+                       Con_Printf("Mod_Q1BSP_LoadLeafs: invalid marksurface range %i:%i outside range %i:%i\n", out->firstmarksurface, out->firstmarksurface + out->nummarksurfaces, 0, loadmodel->brushq1.nummarksurfaces);
+                       out->firstmarksurface = NULL;
+                       out->nummarksurfaces = 0;
+               }
 
                out->pvsdata = pvs;
+               memset(out->pvsdata, 0xFF, pvschainbytes);
                pvs += pvschainbytes;
 
                p = LittleLong(in->visofs);
                if (p >= 0)
-                       Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, out->pvsdata, out->pvsdata + pvschainbytes);
-               else
-                       memset(out->pvsdata, 0xFF, pvschainbytes);
+               {
+                       if (p >= loadmodel->brushq1.num_compressedpvs)
+                               Con_Printf("Mod_Q1BSP_LoadLeafs: invalid visofs\n");
+                       else
+                               Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, out->pvsdata, out->pvsdata + pvschainbytes);
+               }
 
                for (j = 0;j < 4;j++)
                        out->ambient_sound_level[j] = in->ambient_level[j];
@@ -1993,293 +2036,6 @@ static void Mod_Q1BSP_LoadPlanes(lump_t *l)
        }
 }
 
-#define MAX_POINTS_ON_WINDING 64
-
-typedef struct
-{
-       int numpoints;
-       int padding;
-       double points[8][3]; // variable sized
-}
-winding_t;
-
-/*
-==================
-NewWinding
-==================
-*/
-static winding_t *NewWinding(int points)
-{
-       winding_t *w;
-       int size;
-
-       if (points > MAX_POINTS_ON_WINDING)
-               Sys_Error("NewWinding: too many points\n");
-
-       size = sizeof(winding_t) + sizeof(double[3]) * (points - 8);
-       w = Mem_Alloc(loadmodel->mempool, size);
-       memset(w, 0, size);
-
-       return w;
-}
-
-static void FreeWinding(winding_t *w)
-{
-       Mem_Free(w);
-}
-
-/*
-=================
-BaseWindingForPlane
-=================
-*/
-static winding_t *BaseWindingForPlane(mplane_t *p)
-{
-       double org[3], vright[3], vup[3], normal[3];
-       winding_t *w;
-
-       VectorCopy(p->normal, normal);
-       VectorVectorsDouble(normal, vright, vup);
-
-       VectorScale(vup, 1024.0*1024.0*1024.0, vup);
-       VectorScale(vright, 1024.0*1024.0*1024.0, vright);
-
-       // project a really big axis aligned box onto the plane
-       w = NewWinding(4);
-
-       VectorScale(p->normal, p->dist, org);
-
-       VectorSubtract(org, vright, w->points[0]);
-       VectorAdd(w->points[0], vup, w->points[0]);
-
-       VectorAdd(org, vright, w->points[1]);
-       VectorAdd(w->points[1], vup, w->points[1]);
-
-       VectorAdd(org, vright, w->points[2]);
-       VectorSubtract(w->points[2], vup, w->points[2]);
-
-       VectorSubtract(org, vright, w->points[3]);
-       VectorSubtract(w->points[3], vup, w->points[3]);
-
-       w->numpoints = 4;
-
-       return w;
-}
-
-/*
-==================
-ClipWinding
-
-Clips the winding to the plane, returning the new winding on the positive side
-Frees the input winding.
-If keepon is true, an exactly on-plane winding will be saved, otherwise
-it will be clipped away.
-==================
-*/
-static winding_t *ClipWinding(winding_t *in, mplane_t *split, int keepon)
-{
-       double  dists[MAX_POINTS_ON_WINDING + 1];
-       int             sides[MAX_POINTS_ON_WINDING + 1];
-       int             counts[3];
-       double  dot;
-       int             i, j;
-       double  *p1, *p2;
-       double  mid[3];
-       winding_t       *neww;
-       int             maxpts;
-
-       counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0;
-
-       // determine sides for each point
-       for (i = 0;i < in->numpoints;i++)
-       {
-               dists[i] = dot = DotProduct(in->points[i], split->normal) - split->dist;
-               if (dot > ON_EPSILON)
-                       sides[i] = SIDE_FRONT;
-               else if (dot < -ON_EPSILON)
-                       sides[i] = SIDE_BACK;
-               else
-                       sides[i] = SIDE_ON;
-               counts[sides[i]]++;
-       }
-       sides[i] = sides[0];
-       dists[i] = dists[0];
-
-       if (keepon && !counts[0] && !counts[1])
-               return in;
-
-       if (!counts[0])
-       {
-               FreeWinding(in);
-               return NULL;
-       }
-       if (!counts[1])
-               return in;
-
-       maxpts = in->numpoints+4;       // can't use counts[0]+2 because of fp grouping errors
-       if (maxpts > MAX_POINTS_ON_WINDING)
-               Sys_Error("ClipWinding: maxpts > MAX_POINTS_ON_WINDING");
-
-       neww = NewWinding(maxpts);
-
-       for (i = 0;i < in->numpoints;i++)
-       {
-               if (neww->numpoints >= maxpts)
-                       Sys_Error("ClipWinding: points exceeded estimate");
-
-               p1 = in->points[i];
-
-               if (sides[i] == SIDE_ON)
-               {
-                       VectorCopy(p1, neww->points[neww->numpoints]);
-                       neww->numpoints++;
-                       continue;
-               }
-
-               if (sides[i] == SIDE_FRONT)
-               {
-                       VectorCopy(p1, neww->points[neww->numpoints]);
-                       neww->numpoints++;
-               }
-
-               if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
-                       continue;
-
-               // generate a split point
-               p2 = in->points[(i+1)%in->numpoints];
-
-               dot = dists[i] / (dists[i]-dists[i+1]);
-               for (j = 0;j < 3;j++)
-               {       // avoid round off error when possible
-                       if (split->normal[j] == 1)
-                               mid[j] = split->dist;
-                       else if (split->normal[j] == -1)
-                               mid[j] = -split->dist;
-                       else
-                               mid[j] = p1[j] + dot* (p2[j]-p1[j]);
-               }
-
-               VectorCopy(mid, neww->points[neww->numpoints]);
-               neww->numpoints++;
-       }
-
-       // free the original winding
-       FreeWinding(in);
-
-       return neww;
-}
-
-
-/*
-==================
-DivideWinding
-
-Divides a winding by a plane, producing one or two windings.  The
-original winding is not damaged or freed.  If only on one side, the
-returned winding will be the input winding.  If on both sides, two
-new windings will be created.
-==================
-*/
-static void DivideWinding(winding_t *in, mplane_t *split, winding_t **front, winding_t **back)
-{
-       double  dists[MAX_POINTS_ON_WINDING + 1];
-       int             sides[MAX_POINTS_ON_WINDING + 1];
-       int             counts[3];
-       double  dot;
-       int             i, j;
-       double  *p1, *p2;
-       double  mid[3];
-       winding_t       *f, *b;
-       int             maxpts;
-
-       counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0;
-
-       // determine sides for each point
-       for (i = 0;i < in->numpoints;i++)
-       {
-               dot = DotProduct(in->points[i], split->normal);
-               dot -= split->dist;
-               dists[i] = dot;
-               if (dot > ON_EPSILON) sides[i] = SIDE_FRONT;
-               else if (dot < -ON_EPSILON) sides[i] = SIDE_BACK;
-               else sides[i] = SIDE_ON;
-               counts[sides[i]]++;
-       }
-       sides[i] = sides[0];
-       dists[i] = dists[0];
-
-       *front = *back = NULL;
-
-       if (!counts[0])
-       {
-               *back = in;
-               return;
-       }
-       if (!counts[1])
-       {
-               *front = in;
-               return;
-       }
-
-       maxpts = in->numpoints+4;       // can't use counts[0]+2 because of fp grouping errors
-
-       if (maxpts > MAX_POINTS_ON_WINDING)
-               Sys_Error("ClipWinding: maxpts > MAX_POINTS_ON_WINDING");
-
-       *front = f = NewWinding(maxpts);
-       *back = b = NewWinding(maxpts);
-
-       for (i = 0;i < in->numpoints;i++)
-       {
-               if (f->numpoints >= maxpts || b->numpoints >= maxpts)
-                       Sys_Error("DivideWinding: points exceeded estimate");
-
-               p1 = in->points[i];
-
-               if (sides[i] == SIDE_ON)
-               {
-                       VectorCopy(p1, f->points[f->numpoints]);
-                       f->numpoints++;
-                       VectorCopy(p1, b->points[b->numpoints]);
-                       b->numpoints++;
-                       continue;
-               }
-
-               if (sides[i] == SIDE_FRONT)
-               {
-                       VectorCopy(p1, f->points[f->numpoints]);
-                       f->numpoints++;
-               }
-               else if (sides[i] == SIDE_BACK)
-               {
-                       VectorCopy(p1, b->points[b->numpoints]);
-                       b->numpoints++;
-               }
-
-               if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
-                       continue;
-
-               // generate a split point
-               p2 = in->points[(i+1)%in->numpoints];
-
-               dot = dists[i] / (dists[i]-dists[i+1]);
-               for (j = 0;j < 3;j++)
-               {       // avoid round off error when possible
-                       if (split->normal[j] == 1)
-                               mid[j] = split->dist;
-                       else if (split->normal[j] == -1)
-                               mid[j] = -split->dist;
-                       else
-                               mid[j] = p1[j] + dot* (p2[j]-p1[j]);
-               }
-
-               VectorCopy(mid, f->points[f->numpoints]);
-               f->numpoints++;
-               VectorCopy(mid, b->points[b->numpoints]);
-               b->numpoints++;
-       }
-}
-
 typedef struct portal_s
 {
        mplane_t plane;
@@ -2455,7 +2211,7 @@ static void Mod_Q1BSP_FinalizePortals(void)
                                // advance to next portal
                                portal++;
                        }
-                       FreeWinding(p->winding);
+                       Winding_Free(p->winding);
                }
                FreePortal(p);
                p = pnext;
@@ -2557,9 +2313,9 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
        // create the new portal by generating a polygon for the node plane,
        // and clipping it by all of the other portals(which came from nodes above this one)
        nodeportal = AllocPortal();
-       nodeportal->plane = *node->plane;
+       nodeportal->plane = *plane;
 
-       nodeportalwinding = BaseWindingForPlane(node->plane);
+       nodeportalwinding = Winding_NewFromPlane(nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist);
        side = 0;       // shut up compiler warning
        for (portal = (portal_t *)node->portals;portal;portal = portal->next[side])
        {
@@ -2577,7 +2333,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                else
                        Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal");
 
-               nodeportalwinding = ClipWinding(nodeportalwinding, &clipplane, true);
+               nodeportalwinding = Winding_Clip(nodeportalwinding, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, true);
                if (!nodeportalwinding)
                {
                        Con_Printf("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n");
@@ -2610,7 +2366,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                RemovePortalFromNodes(portal);
 
                // cut the portal into two portals, one on each side of the node plane
-               DivideWinding(portal->winding, plane, &frontwinding, &backwinding);
+               Winding_Divide(portal->winding, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, &frontwinding, &backwinding);
 
                if (!frontwinding)
                {
@@ -2635,7 +2391,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                *splitportal = *portal;
                splitportal->chain = temp;
                splitportal->winding = backwinding;
-               FreeWinding(portal->winding);
+               Winding_Free(portal->winding);
                portal->winding = frontwinding;
 
                if (side == 0)
@@ -2654,7 +2410,6 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
        Mod_Q1BSP_RecursiveNodePortals(back);
 }
 
-
 static void Mod_Q1BSP_MakePortals(void)
 {
        portalchain = NULL;
@@ -2797,48 +2552,57 @@ static void Mod_Q1BSP_BuildPVSTextureChains(model_t *model)
        }
 }
 
-void Mod_Q1BSP_FatPVS_RecursiveBSPNode(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbytes, mnode_t *node)
+static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbytes, mnode_t *node)
 {
        int i;
-       mplane_t *plane;
        float d;
 
-       while (1)
+       while (node->contents >= 0)
        {
-       // if this is a leaf, accumulate the pvs bits
-               if (node->contents < 0)
-               {
-                       if (node->contents != CONTENTS_SOLID && ((mleaf_t *)node)->pvsdata)
-                               for (i = 0;i < pvsbytes;i++)
-                                       pvsbuffer[i] |= ((mleaf_t *)node)->pvsdata[i];
-                       return;
-               }
-
-               plane = node->plane;
-               d = DotProduct(org, plane->normal) - plane->dist;
+               d = PlaneDiff(org, node->plane);
                if (d > radius)
                        node = node->children[0];
                else if (d < -radius)
                        node = node->children[1];
                else
-               {       // go down both
+               {
+                       // go down both sides
                        Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]);
                        node = node->children[1];
                }
        }
+       // FIXME: code!
+       // if this is a leaf, accumulate the pvs bits
+       if (node->contents != CONTENTS_SOLID && ((mleaf_t *)node)->pvsdata)
+               for (i = 0;i < pvsbytes;i++)
+                       pvsbuffer[i] |= ((mleaf_t *)node)->pvsdata[i];
 }
 
 //Calculates a PVS that is the inclusive or of all leafs within radius pixels
 //of the given point.
-int Mod_Q1BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbufferlength)
+static int Mod_Q1BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbufferlength)
 {
        int bytes = ((model->brushq1.numleafs - 1) + 7) >> 3;
        bytes = min(bytes, pvsbufferlength);
        memset(pvsbuffer, 0, bytes);
-       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, sv.worldmodel->brushq1.nodes);
+       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brushq1.nodes);
        return bytes;
 }
 
+//Returns PVS data for a given point
+//(note: always returns valid data, never NULL)
+static qbyte *Mod_Q1BSP_GetPVS(model_t *model, const vec3_t p)
+{
+       mnode_t *node;
+       Mod_CheckLoaded(model);
+       // LordHavoc: modified to start at first clip node,
+       // in other words: first node of the (sub)model
+       node = model->brushq1.nodes + model->brushq1.hulls[0].firstclipnode;
+       while (node->contents == 0)
+               node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
+       return ((mleaf_t *)node)->pvsdata;
+}
+
 static void Mod_Q1BSP_RoundUpToHullSize(model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs)
 {
        vec3_t size;
@@ -2886,7 +2650,6 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
        model_t *originalloadmodel;
        float dist, modelyawradius, modelradius, *vec;
        msurface_t *surf;
-       surfmesh_t *mesh;
 
        mod->type = mod_brush;
 
@@ -2897,12 +2660,15 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                Host_Error("Mod_Q1BSP_Load: %s has wrong version number(%i should be %i(Quake) or 30(HalfLife))", mod->name, i, BSPVERSION);
        mod->brush.ishlbsp = i == 30;
 
-       mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
+       mod->brush.SuperContentsFromNativeContents = Mod_Q1BSP_SuperContentsFromNativeContents;
+       mod->brush.NativeContentsFromSuperContents = Mod_Q1BSP_NativeContentsFromSuperContents;
+       mod->brush.GetPVS = Mod_Q1BSP_GetPVS;
        mod->brush.FatPVS = Mod_Q1BSP_FatPVS;
        mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS;
        mod->brush.LightPoint = Mod_Q1BSP_LightPoint;
        mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
        mod->brush.TraceBox = Mod_Q1BSP_TraceBox;
+       mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
        mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize;
        mod->brushq1.PointInLeaf = Mod_Q1BSP_PointInLeaf;
        mod->brushq1.BuildPVSTextureChains = Mod_Q1BSP_BuildPVSTextureChains;
@@ -2980,7 +2746,6 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                // this gets altered below if sky is used
                mod->DrawSky = NULL;
                mod->Draw = R_Model_Brush_Draw;
-               mod->DrawFakeShadow = NULL;
                mod->DrawShadowVolume = R_Model_Brush_DrawShadowVolume;
                mod->DrawLight = R_Model_Brush_DrawLight;
                mod->brushq1.pvstexturechains = Mem_Alloc(originalloadmodel->mempool, mod->brushq1.numtextures * sizeof(msurface_t **));
@@ -3004,23 +2769,20 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                                if (mod->brush.numsubmodels - 1)
                                        surf->flags |= SURF_SOLIDCLIP;
                                // calculate bounding shapes
-                               for (mesh = surf->mesh;mesh;mesh = mesh->chain)
+                               for (k = 0, vec = surf->mesh.data_vertex3f;k < surf->mesh.num_vertices;k++, vec += 3)
                                {
-                                       for (k = 0, vec = mesh->vertex3f;k < mesh->numverts;k++, vec += 3)
-                                       {
-                                               if (mod->normalmins[0] > vec[0]) mod->normalmins[0] = vec[0];
-                                               if (mod->normalmins[1] > vec[1]) mod->normalmins[1] = vec[1];
-                                               if (mod->normalmins[2] > vec[2]) mod->normalmins[2] = vec[2];
-                                               if (mod->normalmaxs[0] < vec[0]) mod->normalmaxs[0] = vec[0];
-                                               if (mod->normalmaxs[1] < vec[1]) mod->normalmaxs[1] = vec[1];
-                                               if (mod->normalmaxs[2] < vec[2]) mod->normalmaxs[2] = vec[2];
-                                               dist = vec[0]*vec[0]+vec[1]*vec[1];
-                                               if (modelyawradius < dist)
-                                                       modelyawradius = dist;
-                                               dist += vec[2]*vec[2];
-                                               if (modelradius < dist)
-                                                       modelradius = dist;
-                                       }
+                                       if (mod->normalmins[0] > vec[0]) mod->normalmins[0] = vec[0];
+                                       if (mod->normalmins[1] > vec[1]) mod->normalmins[1] = vec[1];
+                                       if (mod->normalmins[2] > vec[2]) mod->normalmins[2] = vec[2];
+                                       if (mod->normalmaxs[0] < vec[0]) mod->normalmaxs[0] = vec[0];
+                                       if (mod->normalmaxs[1] < vec[1]) mod->normalmaxs[1] = vec[1];
+                                       if (mod->normalmaxs[2] < vec[2]) mod->normalmaxs[2] = vec[2];
+                                       dist = vec[0]*vec[0]+vec[1]*vec[1];
+                                       if (modelyawradius < dist)
+                                               modelyawradius = dist;
+                                       dist += vec[2]*vec[2];
+                                       if (modelradius < dist)
+                                               modelradius = dist;
                                }
                        }
                        modelyawradius = sqrt(modelyawradius);
@@ -3441,7 +3203,7 @@ static void Mod_Q2BSP_LoadModels(lump_t *l)
 */
 }
 
-void Mod_Q2BSP_Load(model_t *mod, void *buffer)
+void static Mod_Q2BSP_Load(model_t *mod, void *buffer)
 {
        int i;
        q2dheader_t *header;
@@ -3493,6 +3255,8 @@ void Mod_Q2BSP_Load(model_t *mod, void *buffer)
        Mod_Q2BSP_LoadModels(&header->lumps[Q2LUMP_MODELS]);
 }
 
+static int Mod_Q3BSP_SuperContentsFromNativeContents(model_t *model, int nativecontents);
+static int Mod_Q3BSP_NativeContentsFromSuperContents(model_t *model, int supercontents);
 
 static void Mod_Q3BSP_LoadEntities(lump_t *l)
 {
@@ -3551,9 +3315,15 @@ static void Mod_Q3BSP_LoadTextures(lump_t *l)
 
        for (i = 0;i < count;i++, in++, out++)
        {
-               strncpy(out->name, in->name, sizeof(out->name) - 1);
+               strlcpy (out->name, in->name, sizeof (out->name));
                out->surfaceflags = LittleLong(in->surfaceflags);
-               out->contents = LittleLong(in->contents);
+               out->nativecontents = LittleLong(in->contents);
+               out->supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, out->nativecontents);
+               out->renderflags = 0;
+               if (!strcmp(out->name, "caulk") || !strcmp(out->name, "common/caulk") || !strcmp(out->name, "textures/common/caulk"))
+                       out->renderflags |= Q3MTEXTURERENDERFLAGS_NODRAW;
+               if (!strncmp(out->name, "textures/skies/", 15))
+                       out->renderflags |= Q3MTEXTURERENDERFLAGS_SKY;
 
                out->number = i;
                Mod_LoadSkinFrame(&out->skin, out->name, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PRECACHE, false, true, true);
@@ -3617,11 +3387,9 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
 {
        q3dbrush_t *in;
        q3mbrush_t *out;
-       int i, j, k, m, n, c, count, numpoints, numplanes;
-       winding_t *w;
-       mplane_t plane;
-       colpointf_t pointsbuf[256*3];
-       colplanef_t planesbuf[256], colplanef;
+       int i, j, n, c, count, maxplanes;
+       mplane_t *planes;
+       winding_t *temp1, *temp2;
 
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
@@ -3632,6 +3400,12 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
        loadmodel->brushq3.data_brushes = out;
        loadmodel->brushq3.num_brushes = count;
 
+       temp1 = Winding_New(64);
+       temp2 = Winding_New(64);
+
+       maxplanes = 0;
+       planes = NULL;
+
        for (i = 0;i < count;i++, in++, out++)
        {
                n = LittleLong(in->firstbrushside);
@@ -3645,86 +3419,26 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
                        Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)\n", n, loadmodel->brushq3.num_textures);
                out->texture = loadmodel->brushq3.data_textures + n;
 
-               // construct a collision brush, which needs points and planes...
-               // each point and plane should be unique, and they don't refer to
-               // eachother in any way, so keeping them unique is fairly easy
-               numpoints = 0;
-               numplanes = 0;
-               for (j = 0;j < out->numbrushsides;j++)
+               // make a list of mplane_t structs to construct a colbrush from
+               if (maxplanes < out->numbrushsides)
                {
-                       // for some reason the planes are all flipped compared to what I
-                       // would expect, so this has to negate them...
-
-                       // create a huge polygon for the plane
-                       VectorNegate(out->firstbrushside[j].plane->normal, plane.normal);
-                       plane.dist = -out->firstbrushside[j].plane->dist;
-                       w = BaseWindingForPlane(&plane);
-                       // clip it by all other planes
-                       for (k = 0;k < out->numbrushsides && w;k++)
-                       {
-                               if (k != j)
-                               {
-                                       VectorNegate(out->firstbrushside[k].plane->normal, plane.normal);
-                                       plane.dist = -out->firstbrushside[k].plane->dist;
-                                       w = ClipWinding(w, &plane, true);
-                               }
-                       }
-                       // if nothing is left, skip it
-                       // FIXME: should keep count of how many were skipped and report
-                       // it, just for sake of statistics
-                       if (!w)
-                               continue;
-                       // add the points uniquely (no duplicates)
-                       for (k = 0;k < w->numpoints;k++)
-                       {
-                               for (m = 0;m < numpoints;m++)
-                                       if (VectorDistance2(w->points[k], pointsbuf[m].v) < DIST_EPSILON)
-                                               break;
-                               if (m == numpoints)
-                               {
-                                       // check if there are too many and skip the brush
-                                       if (numpoints >= 256)
-                                       {
-                                               Con_Printf("Mod_Q3BSP_LoadBrushes: failed to build collision brush: too many points for buffer\n");
-                                               FreeWinding(w);
-                                               goto failedtomakecolbrush;
-                                       }
-                                       // add the new one
-                                       VectorCopy(w->points[k], pointsbuf[numpoints].v);
-                                       numpoints++;
-                               }
-                       }
-                       // add the plane uniquely (no duplicates)
-                       memset(&colplanef, 0, sizeof(colplanef));
-                       VectorCopy(out->firstbrushside[k].plane->normal, colplanef.normal);
-                       colplanef.dist = out->firstbrushside[k].plane->dist;
-                       for (k = 0;k < numplanes;k++)
-                               if (VectorCompare(planesbuf[k].normal, colplanef.normal) && planesbuf[k].dist == colplanef.dist)
-                                       break;
-                       if (k == numplanes)
-                       {
-                               // check if there are too many and skip the brush
-                               if (numplanes >= 256)
-                               {
-                                       Con_Printf("Mod_Q3BSP_LoadBrushes: failed to build collision brush: too many planes for buffer\n");
-                                       FreeWinding(w);
-                                       goto failedtomakecolbrush;
-                               }
-                               // add the new one
-                               planesbuf[numplanes++] = colplanef;
-                       }
-                       FreeWinding(w);
+                       maxplanes = out->numbrushsides;
+                       if (planes)
+                               Mem_Free(planes);
+                       planes = Mem_Alloc(tempmempool, sizeof(mplane_t) * maxplanes);
                }
-               // if anything is left, create the collision brush
-               if (numplanes && numpoints)
+               for (j = 0;j < out->numbrushsides;j++)
                {
-                       out->colbrushf = Collision_AllocBrushFloat(loadmodel->mempool, numpoints, numplanes);
-                       memcpy(out->colbrushf->points, pointsbuf, numpoints * sizeof(colpointf_t));
-                       memcpy(out->colbrushf->planes, planesbuf, numplanes * sizeof(colplanef_t));
+                       VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal);
+                       planes[j].dist = out->firstbrushside[j].plane->dist;
                }
-               // return from errors to here
-               failedtomakecolbrush:;
+               // make the colbrush from the planes
+               out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, temp1, temp2);
        }
+       if (planes)
+               Mem_Free(planes);
+       Winding_Free(temp1);
+       Winding_Free(temp2);
 }
 
 static void Mod_Q3BSP_LoadEffects(lump_t *l)
@@ -3744,7 +3458,7 @@ static void Mod_Q3BSP_LoadEffects(lump_t *l)
 
        for (i = 0;i < count;i++, in++, out++)
        {
-               strncpy(out->shadername, in->shadername, sizeof(out->shadername) - 1);
+               strlcpy (out->shadername, in->shadername, sizeof (out->shadername));
                n = LittleLong(in->brushindex);
                if (n < 0 || n >= loadmodel->brushq3.num_brushes)
                        Host_Error("Mod_Q3BSP_LoadEffects: invalid brushindex %i (%i brushes)\n", n, loadmodel->brushq3.num_brushes);
@@ -3762,13 +3476,13 @@ static void Mod_Q3BSP_LoadVertices(lump_t *l)
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadVertices: funny lump size in %s",loadmodel->name);
        loadmodel->brushq3.num_vertices = count = l->filelen / sizeof(*in);
-       loadmodel->brushq3.data_vertex3f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[3]));
-       loadmodel->brushq3.data_texcoordtexture2f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[2]));
-       loadmodel->brushq3.data_texcoordlightmap2f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[2]));
-       loadmodel->brushq3.data_svector3f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[3]));
-       loadmodel->brushq3.data_tvector3f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[3]));
-       loadmodel->brushq3.data_normal3f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[3]));
-       loadmodel->brushq3.data_color4f = Mem_Alloc(loadmodel->mempool, count * sizeof(float[4]));
+       loadmodel->brushq3.data_vertex3f = Mem_Alloc(loadmodel->mempool, count * (sizeof(float) * (3 + 2 + 2 + 3 + 3 + 3 + 4)));
+       loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_vertex3f + count * 3;
+       loadmodel->brushq3.data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordtexture2f + count * 2;
+       loadmodel->brushq3.data_svector3f = loadmodel->brushq3.data_texcoordlightmap2f + count * 2;
+       loadmodel->brushq3.data_tvector3f = loadmodel->brushq3.data_svector3f + count * 3;
+       loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_tvector3f + count * 3;
+       loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_normal3f + count * 3;
 
        for (i = 0;i < count;i++, in++)
        {
@@ -3806,17 +3520,20 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
        if (l->filelen % sizeof(int[3]))
                Host_Error("Mod_Q3BSP_LoadTriangles: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out) * 2);
 
        loadmodel->brushq3.num_triangles = count / 3;
        loadmodel->brushq3.data_element3i = out;
-       loadmodel->brushq3.data_neighbor3i = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       loadmodel->brushq3.data_neighbor3i = out + count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
                *out = LittleLong(*in);
                if (*out < 0 || *out >= loadmodel->brushq3.num_vertices)
-                       Host_Error("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices)\n", *out, loadmodel->brushq3.num_vertices);
+               {
+                       Con_Printf("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices), setting to 0\n", *out, loadmodel->brushq3.num_vertices);
+                       *out = 0;
+               }
        }
 }
 
@@ -3826,6 +3543,8 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l)
        rtexture_t **out;
        int i, count;
 
+       if (!l->filelen)
+               return;
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name);
@@ -3843,7 +3562,17 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
 {
        q3dface_t *in;
        q3mface_t *out;
-       int i, j, n, count, invalidelements, patchsize[2];
+       int i, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xlevel, ylevel, row0, row1, x, y, *e, finalvertices, finaltriangles;
+       //int *originalelement3i;
+       //int *originalneighbor3i;
+       float *originalvertex3f;
+       //float *originalsvector3f;
+       //float *originaltvector3f;
+       //float *originalnormal3f;
+       float *originalcolor4f;
+       float *originaltexcoordtexture2f;
+       float *originaltexcoordlightmap2f;
+       float *v;
 
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
@@ -3899,41 +3628,41 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n];
 
                out->firstvertex = LittleLong(in->firstvertex);
-               out->numvertices = LittleLong(in->numvertices);
+               out->num_vertices = LittleLong(in->numvertices);
                out->firstelement = LittleLong(in->firstelement);
-               out->numelements = LittleLong(in->numelements);
-               out->numtriangles = out->numelements / 3;
-               if (out->firstvertex < 0 || out->firstvertex + out->numvertices > loadmodel->brushq3.num_vertices)
+               out->num_triangles = LittleLong(in->numelements) / 3;
+               if (out->num_triangles * 3 != LittleLong(in->numelements))
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, out->firstvertex, out->firstvertex + out->numvertices, loadmodel->brushq3.num_vertices);
+                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): numelements %i is not a multiple of 3\n", i, out->texture->name, LittleLong(in->numelements));
                        out->type = 0; // error
                        continue;
                }
-               if (out->firstelement < 0 || out->firstelement + out->numelements > loadmodel->brushq3.num_triangles * 3)
+               if (out->firstvertex < 0 || out->firstvertex + out->num_vertices > loadmodel->brushq3.num_vertices)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, out->firstelement, out->firstelement + out->numelements, loadmodel->brushq3.num_triangles * 3);
+                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, out->firstvertex, out->firstvertex + out->num_vertices, loadmodel->brushq3.num_vertices);
                        out->type = 0; // error
                        continue;
                }
-               if (out->numtriangles * 3 != out->numelements)
+               if (out->firstelement < 0 || out->firstelement + out->num_triangles * 3 > loadmodel->brushq3.num_triangles * 3)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): numelements %i is not a multiple of 3\n", i, out->texture->name, out->numelements);
+                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, out->firstelement, out->firstelement + out->num_triangles * 3, loadmodel->brushq3.num_triangles * 3);
                        out->type = 0; // error
                        continue;
                }
+               out->data_vertex3f = loadmodel->brushq3.data_vertex3f + out->firstvertex * 3;
+               out->data_texcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + out->firstvertex * 2;
+               out->data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + out->firstvertex * 2;
+               out->data_svector3f = loadmodel->brushq3.data_svector3f + out->firstvertex * 3;
+               out->data_tvector3f = loadmodel->brushq3.data_tvector3f + out->firstvertex * 3;
+               out->data_normal3f = loadmodel->brushq3.data_normal3f + out->firstvertex * 3;
+               out->data_color4f = loadmodel->brushq3.data_color4f + out->firstvertex * 4;
+               out->data_element3i = loadmodel->brushq3.data_element3i + out->firstelement;
+               out->data_neighbor3i = loadmodel->brushq3.data_neighbor3i + out->firstelement;
                switch(out->type)
                {
                case Q3FACETYPE_POLYGON:
                case Q3FACETYPE_MESH:
-                       out->data_vertex3f = loadmodel->brushq3.data_vertex3f + out->firstvertex * 3;
-                       out->data_texcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + out->firstvertex * 2;
-                       out->data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + out->firstvertex * 2;
-                       out->data_svector3f = loadmodel->brushq3.data_svector3f + out->firstvertex * 3;
-                       out->data_tvector3f = loadmodel->brushq3.data_tvector3f + out->firstvertex * 3;
-                       out->data_normal3f = loadmodel->brushq3.data_normal3f + out->firstvertex * 3;
-                       out->data_color4f = loadmodel->brushq3.data_color4f + out->firstvertex * 4;
-                       out->data_element3i = loadmodel->brushq3.data_element3i + out->firstelement;
-                       out->data_neighbor3i = loadmodel->brushq3.data_neighbor3i + out->firstelement;
+                       // no processing necessary
                        break;
                case Q3FACETYPE_PATCH:
                        patchsize[0] = LittleLong(in->specific.patch.patchsize[0]);
@@ -3944,31 +3673,118 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                out->type = 0; // error
                                continue;
                        }
-                       // FIXME: convert patch to triangles here!
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_PATCH not supported (yet)\n", i, out->texture->name);
-                       out->type = 0;
-                       continue;
+                       // convert patch to Q3FACETYPE_MESH
+                       xlevel = mod_q3bsp_curves_subdivide_level.integer;
+                       ylevel = mod_q3bsp_curves_subdivide_level.integer;
+                       finalwidth = ((patchsize[0] - 1) << xlevel) + 1;
+                       finalheight = ((patchsize[1] - 1) << ylevel) + 1;
+                       finalvertices = finalwidth * finalheight;
+                       finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+                       originalvertex3f = out->data_vertex3f;
+                       //originalsvector3f = out->data_svector3f;
+                       //originaltvector3f = out->data_tvector3f;
+                       //originalnormal3f = out->data_normal3f;
+                       originalcolor4f = out->data_color4f;
+                       originaltexcoordtexture2f = out->data_texcoordtexture2f;
+                       originaltexcoordlightmap2f = out->data_texcoordlightmap2f;
+                       //originalelement3i = out->data_element3i;
+                       //originalneighbor3i = out->data_neighbor3i;
+                       out->data_vertex3f = Mem_Alloc(loadmodel->mempool, sizeof(float[20]) * finalvertices + sizeof(int[6]) * finaltriangles);
+                       out->data_svector3f = out->data_vertex3f + finalvertices * 3;
+                       out->data_tvector3f = out->data_svector3f + finalvertices * 3;
+                       out->data_normal3f = out->data_tvector3f + finalvertices * 3;
+                       out->data_color4f = out->data_normal3f + finalvertices * 3;
+                       out->data_texcoordtexture2f = out->data_color4f + finalvertices * 4;
+                       out->data_texcoordlightmap2f = out->data_texcoordtexture2f + finalvertices * 2;
+                       out->data_element3i = (int *)(out->data_texcoordlightmap2f + finalvertices * 2);
+                       out->data_neighbor3i = out->data_element3i + finaltriangles * 3;
+                       out->type = Q3FACETYPE_MESH;
+                       out->firstvertex = -1;
+                       out->num_vertices = finalvertices;
+                       out->firstelement = -1;
+                       out->num_triangles = finaltriangles;
+                       // generate geometry
+                       // (note: normals are skipped because they get recalculated)
+                       QuadraticSplinePatchSubdivideFloatBuffer(patchsize[0], patchsize[1], xlevel, ylevel, 3, originalvertex3f, out->data_vertex3f);
+                       QuadraticSplinePatchSubdivideFloatBuffer(patchsize[0], patchsize[1], xlevel, ylevel, 2, originaltexcoordtexture2f, out->data_texcoordtexture2f);
+                       QuadraticSplinePatchSubdivideFloatBuffer(patchsize[0], patchsize[1], xlevel, ylevel, 2, originaltexcoordlightmap2f, out->data_texcoordlightmap2f);
+                       QuadraticSplinePatchSubdivideFloatBuffer(patchsize[0], patchsize[1], xlevel, ylevel, 4, originalcolor4f, out->data_color4f);
+                       // generate elements
+                       e = out->data_element3i;
+                       for (y = 0;y < finalheight - 1;y++)
+                       {
+                               row0 = (y + 0) * finalwidth;
+                               row1 = (y + 1) * finalwidth;
+                               for (x = 0;x < finalwidth - 1;x++)
+                               {
+                                       *e++ = row0;
+                                       *e++ = row1;
+                                       *e++ = row0 + 1;
+                                       *e++ = row1;
+                                       *e++ = row1 + 1;
+                                       *e++ = row0 + 1;
+                                       row0++;
+                                       row1++;
+                               }
+                       }
+                       out->num_triangles = Mod_RemoveDegenerateTriangles(out->num_triangles, out->data_element3i, out->data_element3i, out->data_vertex3f);
+                       if (developer.integer)
+                       {
+                               if (out->num_triangles < finaltriangles)
+                                       Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles, %i degenerate triangles removed (leaving %i)\n", patchsize[0], patchsize[1], out->num_vertices, finaltriangles, finaltriangles - out->num_triangles, out->num_triangles);
+                               else
+                                       Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_vertices, out->num_triangles);
+                       }
+                       // q3map does not put in collision brushes for curves... ugh
+                       out->collisions = true;
                        break;
                case Q3FACETYPE_FLARE:
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name);
-                       out->type = 0;
-                       continue;
+                       Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name);
+                       // don't render it
+                       out->num_triangles = 0;
                        break;
                }
-               for (j = 0, invalidelements = 0;j < out->numelements;j++)
-                       if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->numvertices)
+               for (j = 0, invalidelements = 0;j < out->num_triangles * 3;j++)
+                       if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->num_vertices)
                                invalidelements++;
                if (invalidelements)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, texture->contents = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, out->type, out->texture->name, out->texture->surfaceflags, out->texture->contents, out->firstvertex, out->numvertices, out->firstelement, out->numelements);
-                       for (j = 0;j < out->numelements;j++)
+                       Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, texture->nativecontents = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, out->type, out->texture->name, out->texture->surfaceflags, out->texture->nativecontents, out->firstvertex, out->num_vertices, out->firstelement, out->num_triangles * 3);
+                       for (j = 0;j < out->num_triangles * 3;j++)
                        {
                                Con_Printf(" %i", out->data_element3i[j]);
-                               if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->numvertices)
+                               if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->num_vertices)
                                        out->data_element3i[j] = 0;
                        }
                        Con_Printf("\n");
                }
+               // for shadow volumes
+               Mod_BuildTriangleNeighbors(out->data_neighbor3i, out->data_element3i, out->num_triangles);
+               // for per pixel lighting
+               Mod_BuildTextureVectorsAndNormals(out->num_vertices, out->num_triangles, out->data_vertex3f, out->data_texcoordtexture2f, out->data_element3i, out->data_svector3f, out->data_tvector3f, out->data_normal3f);
+               // calculate a bounding box
+               VectorClear(out->mins);
+               VectorClear(out->maxs);
+               if (out->num_vertices)
+               {
+                       VectorCopy(out->data_vertex3f, out->mins);
+                       VectorCopy(out->data_vertex3f, out->maxs);
+                       for (j = 1, v = out->data_vertex3f + 3;j < out->num_vertices;j++, v += 3)
+                       {
+                               out->mins[0] = min(out->mins[0], v[0]);
+                               out->maxs[0] = max(out->maxs[0], v[0]);
+                               out->mins[1] = min(out->mins[1], v[1]);
+                               out->maxs[1] = max(out->maxs[1], v[1]);
+                               out->mins[2] = min(out->mins[2], v[2]);
+                               out->maxs[2] = max(out->maxs[2], v[2]);
+                       }
+                       out->mins[0] -= 1.0f;
+                       out->mins[1] -= 1.0f;
+                       out->mins[2] -= 1.0f;
+                       out->maxs[0] += 1.0f;
+                       out->maxs[1] += 1.0f;
+                       out->maxs[2] += 1.0f;
+               }
        }
 }
 
@@ -4145,13 +3961,18 @@ static void Mod_Q3BSP_LoadNodes(lump_t *l)
                        }
                        else
                        {
-                               n = 1 - n;
+                               n = -1 - n;
                                if (n >= loadmodel->brushq3.num_leafs)
                                        Host_Error("Mod_Q3BSP_LoadNodes: invalid child leaf index %i (%i leafs)\n", n, loadmodel->brushq3.num_leafs);
                                out->children[j] = (q3mnode_t *)(loadmodel->brushq3.data_leafs + n);
                        }
                }
-               // we don't load the mins/maxs
+               for (j = 0;j < 3;j++)
+               {
+                       // yes the mins/maxs are ints
+                       out->mins[j] = LittleLong(in->mins[j]);
+                       out->maxs[j] = LittleLong(in->maxs[j]);
+               }
        }
 
        // set the parent pointers
@@ -4164,6 +3985,9 @@ static void Mod_Q3BSP_LoadLightGrid(lump_t *l)
        q3dlightgrid_t *out;
        int count;
 
+       if (l->filelen == 0)
+               return;
+
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLightGrid: funny lump size in %s",loadmodel->name);
@@ -4201,6 +4025,9 @@ static void Mod_Q3BSP_LoadPVS(lump_t *l)
        q3dpvs_t *in;
        int totalchains;
 
+       if (l->filelen == 0)
+               return;
+
        in = (void *)(mod_base + l->fileofs);
        if (l->filelen < 9)
                Host_Error("Mod_Q3BSP_LoadPVS: funny lump size in %s",loadmodel->name);
@@ -4217,40 +4044,191 @@ static void Mod_Q3BSP_LoadPVS(lump_t *l)
        memcpy(loadmodel->brushq3.data_pvschains, (qbyte *)(in + 1), totalchains);
 }
 
-void Mod_Q3BSP_FindNonSolidLocation(model_t *model, const vec3_t in, vec3_t out, vec_t radius)
+static void Mod_Q3BSP_FindNonSolidLocation(model_t *model, const vec3_t in, vec3_t out, vec_t radius)
 {
        // FIXME: finish this code
        VectorCopy(in, out);
 }
 
-void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, q3mnode_t *node, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end)
+static void Mod_Q3BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal)
 {
+       int i, j, k, index[3];
+       float transformed[3], blend1, blend2, blend, yaw, pitch, sinpitch;
+       q3dlightgrid_t *a, *s;
+       // FIXME: write this
+       if (!model->brushq3.num_lightgrid)
+       {
+               ambientcolor[0] += 128;
+               ambientcolor[1] += 128;
+               ambientcolor[2] += 128;
+               return;
+       }
+       Matrix4x4_Transform(&model->brushq3.num_lightgrid_indexfromworld, p, transformed);
+       //Matrix4x4_Print(&model->brushq3.num_lightgrid_indexfromworld);
+       //Con_Printf("%f %f %f transformed %f %f %f clamped ", p[0], p[1], p[2], transformed[0], transformed[1], transformed[2]);
+       transformed[0] = bound(0, transformed[0], model->brushq3.num_lightgrid_isize[0] - 1);
+       transformed[1] = bound(0, transformed[1], model->brushq3.num_lightgrid_isize[1] - 1);
+       transformed[2] = bound(0, transformed[2], model->brushq3.num_lightgrid_isize[2] - 1);
+       index[0] = (int)floor(transformed[0]);
+       index[1] = (int)floor(transformed[1]);
+       index[2] = (int)floor(transformed[2]);
+       //Con_Printf("%f %f %f index %i %i %i:\n", transformed[0], transformed[1], transformed[2], index[0], index[1], index[2]);
+       // now lerp the values
+       VectorClear(diffusenormal);
+       a = &model->brushq3.data_lightgrid[(index[2] * model->brushq3.num_lightgrid_isize[1] + index[1]) * model->brushq3.num_lightgrid_isize[0] + index[0]];
+       for (k = 0;k < 2;k++)
+       {
+               blend1 = (k ? (transformed[2] - index[2]) : (1 - (transformed[2] - index[2])));
+               if (blend1 < 0.001f || index[2] + k >= model->brushq3.num_lightgrid_isize[2])
+                       continue;
+               for (j = 0;j < 2;j++)
+               {
+                       blend2 = blend1 * (j ? (transformed[1] - index[1]) : (1 - (transformed[1] - index[1])));
+                       if (blend2 < 0.001f || index[1] + j >= model->brushq3.num_lightgrid_isize[1])
+                               continue;
+                       for (i = 0;i < 2;i++)
+                       {
+                               blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0])));
+                               if (blend < 0.001f || index[0] + i >= model->brushq3.num_lightgrid_isize[0])
+                                       continue;
+                               s = a + (k * model->brushq3.num_lightgrid_isize[1] + j) * model->brushq3.num_lightgrid_isize[0] + i;
+                               VectorMA(ambientcolor, blend * (1.0f / 128.0f), s->ambientrgb, ambientcolor);
+                               VectorMA(diffusecolor, blend * (1.0f / 128.0f), s->diffusergb, diffusecolor);
+                               pitch = s->diffusepitch * M_PI / 128;
+                               yaw = s->diffuseyaw * M_PI / 128;
+                               sinpitch = sin(pitch);
+                               diffusenormal[0] += blend * (cos(yaw) * sinpitch);
+                               diffusenormal[1] += blend * (sin(yaw) * sinpitch);
+                               diffusenormal[2] += blend * (cos(pitch));
+                               //Con_Printf("blend %f: ambient %i %i %i, diffuse %i %i %i, diffusepitch %i diffuseyaw %i (%f %f, normal %f %f %f)\n", blend, s->ambientrgb[0], s->ambientrgb[1], s->ambientrgb[2], s->diffusergb[0], s->diffusergb[1], s->diffusergb[2], s->diffusepitch, s->diffuseyaw, pitch, yaw, (cos(yaw) * cospitch), (sin(yaw) * cospitch), (-sin(pitch)));
+                       }
+               }
+       }
+       VectorNormalize(diffusenormal);
+       //Con_Printf("result: ambient %f %f %f diffuse %f %f %f diffusenormal %f %f %f\n", ambientcolor[0], ambientcolor[1], ambientcolor[2], diffusecolor[0], diffusecolor[1], diffusecolor[2], diffusenormal[0], diffusenormal[1], diffusenormal[2]);
+}
+
+static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, q3mnode_t *node, const vec3_t start, const vec3_t end, vec_t startfrac, vec_t endfrac, const vec3_t linestart, const vec3_t lineend, int markframe)
+{
+       int i, startside, endside;
+       float dist1, dist2, midfrac, mid[3], segmentmins[3], segmentmaxs[3];
+       q3mleaf_t *leaf;
+       q3mface_t *face;
+       colbrushf_t *brush;
+       if (startfrac >= trace->fraction)
+               return;
+       // note: all line fragments past first impact fraction are ignored
+       while (node->isnode)
+       {
+               // recurse down node sides
+               dist1 = PlaneDiff(start, node->plane);
+               dist2 = PlaneDiff(end, node->plane);
+               startside = dist1 < 0;
+               endside = dist2 < 0;
+               if (startside == endside)
+               {
+                       // most of the time the line fragment is on one side of the plane
+                       node = node->children[startside];
+               }
+               else
+               {
+                       // line crosses node plane, split the line
+                       midfrac = dist1 / (dist1 - dist2);
+                       VectorLerp(linestart, midfrac, lineend, mid);
+                       // take the near side first
+                       Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, node->children[startside], start, mid, startfrac, midfrac, linestart, lineend, markframe);
+                       if (midfrac < trace->fraction)
+                               Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, node->children[endside], mid, end, midfrac, endfrac, linestart, lineend, markframe);
+                       return;
+               }
+       }
+       // hit a leaf
+       segmentmins[0] = min(start[0], end[0]);
+       segmentmins[1] = min(start[1], end[1]);
+       segmentmins[2] = min(start[2], end[2]);
+       segmentmaxs[0] = max(start[0], end[0]);
+       segmentmaxs[1] = max(start[1], end[1]);
+       segmentmaxs[2] = max(start[2], end[2]);
+       leaf = (q3mleaf_t *)node;
+       for (i = 0;i < leaf->numleafbrushes;i++)
+       {
+               if (startfrac >= trace->fraction)
+                       return;
+               brush = leaf->firstleafbrush[i]->colbrushf;
+               if (brush && brush->markframe != markframe)
+               {
+                       brush->markframe = markframe;
+                       if (BoxesOverlap(segmentmins, segmentmaxs, brush->mins, brush->maxs))
+                               Collision_TraceLineBrushFloat(trace, linestart, lineend, leaf->firstleafbrush[i]->colbrushf, leaf->firstleafbrush[i]->colbrushf);
+               }
+       }
+       if (mod_q3bsp_curves_collisions.integer)
+       {
+               for (i = 0;i < leaf->numleaffaces;i++)
+               {
+                       if (startfrac >= trace->fraction)
+                               return;
+                       face = leaf->firstleafface[i];
+                       if (face->collisions && face->collisionmarkframe != markframe)
+                       {
+                               face->collisionmarkframe = markframe;
+                               if (BoxesOverlap(segmentmins, segmentmaxs, face->mins, face->maxs))
+                                       Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                       }
+               }
+       }
+}
+
+static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, q3mnode_t *node, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs)
+{
+       int i, sides;
+       float nodesegmentmins[3], nodesegmentmaxs[3];
+       q3mleaf_t *leaf;
+       colbrushf_t *brush;
+       q3mface_t *face;
+       nodesegmentmins[0] = max(segmentmins[0], node->mins[0]);
+       nodesegmentmins[1] = max(segmentmins[1], node->mins[1]);
+       nodesegmentmins[2] = max(segmentmins[2], node->mins[2]);
+       nodesegmentmaxs[0] = min(segmentmaxs[0], node->maxs[0]);
+       nodesegmentmaxs[1] = min(segmentmaxs[1], node->maxs[1]);
+       nodesegmentmaxs[2] = min(segmentmaxs[2], node->maxs[2]);
+       if (nodesegmentmins[0] > nodesegmentmaxs[0] || nodesegmentmins[1] > nodesegmentmaxs[1] || nodesegmentmins[2] > nodesegmentmaxs[2])
+               return;
        if (node->isnode)
        {
                // recurse down node sides
-               int i;
-               float dist;
-               colpointf_t *ps, *pe;
-               // FIXME? if TraceBrushPolygonTransform were to be made usable, the
-               // node planes would need to be transformed too
+               sides = BoxOnPlaneSide(segmentmins, segmentmaxs, node->plane);
+               if (sides == 3)
+               {
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[0], thisbrush_start, thisbrush_end, markframe, nodesegmentmins, nodesegmentmaxs);
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[1], thisbrush_start, thisbrush_end, markframe, nodesegmentmins, nodesegmentmaxs);
+               }
+               else if (sides == 2)
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[1], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs);
+               else // sides == 1
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs);
+               /*
                dist = node->plane->dist - (1.0f / 8.0f);
                for (i = 0, ps = thisbrush_start->points, pe = thisbrush_end->points;i < thisbrush_start->numpoints;i++, ps++, pe++)
                {
-                       if (DotProduct(ps->v, node->plane->normal) > dist || DotProduct(pe->v, node->plane->normal) > dist)
+                       if (DotProduct(ps->v, node->plane->normal) >= dist || DotProduct(pe->v, node->plane->normal) >= dist)
                        {
-                               Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[0], thisbrush_start, thisbrush_end);
+                               Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[0], thisbrush_start, thisbrush_end, markframe, nodesegmentmins, nodesegmentmaxs);
                                break;
                        }
                }
+               */
+               /*
                dist = node->plane->dist + (1.0f / 8.0f);
                for (i = 0, ps = thisbrush_start->points, pe = thisbrush_end->points;i < thisbrush_start->numpoints;i++, ps++, pe++)
                {
-                       if (DotProduct(ps->v, node->plane->normal) < dist || DotProduct(pe->v, node->plane->normal) < dist)
+                       if (DotProduct(ps->v, node->plane->normal) <= dist || DotProduct(pe->v, node->plane->normal) <= dist)
                        {
-                               Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[1], thisbrush_start, thisbrush_end);
+                               Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, node->children[1], thisbrush_start, thisbrush_end, markframe, nodesegmentmins, nodesegmentmaxs);
                                break;
                        }
                }
+               */
                /*
                sides = BoxOnPlaneSide(boxstartmins, boxstartmaxs, node->plane) | BoxOnPlaneSide(boxendmins, boxendmaxs, node->plane);
                if (sides & 1)
@@ -4261,41 +4239,93 @@ void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, q3mnode_t *node, cons
        }
        else
        {
-               int i;
-               q3mleaf_t *leaf;
+               // hit a leaf
                leaf = (q3mleaf_t *)node;
                for (i = 0;i < leaf->numleafbrushes;i++)
-                       if (leaf->firstleafbrush[i]->colbrushf)
+               {
+                       brush = leaf->firstleafbrush[i]->colbrushf;
+                       if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs))
+                       {
+                               brush->markframe = markframe;
                                Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, leaf->firstleafbrush[i]->colbrushf, leaf->firstleafbrush[i]->colbrushf);
+                       }
+               }
+               if (mod_q3bsp_curves_collisions.integer)
+               {
+                       for (i = 0;i < leaf->numleaffaces;i++)
+                       {
+                               face = leaf->firstleafface[i];
+                               // note: this can not be optimized with a face->collisionmarkframe because each triangle of the face would need to be marked as done individually (because each one is bbox culled individually), and if all are marked, then the face could be marked as done
+                               if (face->collisions && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, face->mins, face->maxs))
+                                       Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                       }
+               }
        }
 }
 
-void Mod_Q3BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal)
-{
-       // FIXME: write this
-       ambientcolor[0] += 255;
-       ambientcolor[1] += 255;
-       ambientcolor[2] += 255;
-}
-
-void Mod_Q3BSP_TraceBox(model_t *model, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs)
+static void Mod_Q3BSP_TraceBox(model_t *model, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs, int hitsupercontentsmask)
 {
        int i;
+       float segmentmins[3], segmentmaxs[3];
        colbrushf_t *thisbrush_start, *thisbrush_end;
        matrix4x4_t startmatrix, endmatrix;
-       // FIXME: finish this code
-       Matrix4x4_CreateIdentity(&startmatrix);
-       Matrix4x4_CreateIdentity(&endmatrix);
-       thisbrush_start = Collision_BrushForBox(&startmatrix, boxstartmins, boxstartmaxs);
-       thisbrush_end = Collision_BrushForBox(&endmatrix, boxendmins, boxendmaxs);
+       static int markframe = 0;
+       q3mface_t *face;
        memset(trace, 0, sizeof(*trace));
        trace->fraction = 1;
-       if (model->brushq3.num_nodes)
-               Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model->brushq3.data_nodes, thisbrush_start, thisbrush_end);
+       trace->hitsupercontentsmask = hitsupercontentsmask;
+       Matrix4x4_CreateIdentity(&startmatrix);
+       Matrix4x4_CreateIdentity(&endmatrix);
+       segmentmins[0] = min(boxstartmins[0], boxendmins[0]);
+       segmentmins[1] = min(boxstartmins[1], boxendmins[1]);
+       segmentmins[2] = min(boxstartmins[2], boxendmins[2]);
+       segmentmaxs[0] = max(boxstartmaxs[0], boxendmaxs[0]);
+       segmentmaxs[1] = max(boxstartmaxs[1], boxendmaxs[1]);
+       segmentmaxs[2] = max(boxstartmaxs[2], boxendmaxs[2]);
+       if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(boxstartmins, boxstartmaxs) && VectorCompare(boxendmins, boxendmaxs))
+       {
+               // line trace
+               if (model->brushq3.submodel)
+               {
+                       for (i = 0;i < model->brushq3.data_thismodel->numbrushes;i++)
+                               if (model->brushq3.data_thismodel->firstbrush[i].colbrushf)
+                                       Collision_TraceLineBrushFloat(trace, boxstartmins, boxendmins, model->brushq3.data_thismodel->firstbrush[i].colbrushf, model->brushq3.data_thismodel->firstbrush[i].colbrushf);
+                       if (mod_q3bsp_curves_collisions.integer)
+                       {
+                               for (i = 0;i < model->brushq3.data_thismodel->numfaces;i++)
+                               {
+                                       face = model->brushq3.data_thismodel->firstface + i;
+                                       if (face->collisions)
+                                               Collision_TraceLineTriangleMeshFloat(trace, boxstartmins, boxendmins, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                               }
+                       }
+               }
+               else
+                       Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model->brushq3.data_nodes, boxstartmins, boxendmins, 0, 1, boxstartmins, boxendmins, ++markframe);
+       }
        else
-               for (i = 0;i < model->brushq3.num_brushes;i++)
-                       if (model->brushq3.data_brushes[i].colbrushf)
-                               Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, model->brushq3.data_brushes[i].colbrushf, model->brushq3.data_brushes[i].colbrushf);
+       {
+               // box trace, performed as brush trace
+               thisbrush_start = Collision_BrushForBox(&startmatrix, boxstartmins, boxstartmaxs);
+               thisbrush_end = Collision_BrushForBox(&endmatrix, boxendmins, boxendmaxs);
+               if (model->brushq3.submodel)
+               {
+                       for (i = 0;i < model->brushq3.data_thismodel->numbrushes;i++)
+                               if (model->brushq3.data_thismodel->firstbrush[i].colbrushf)
+                                       Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, model->brushq3.data_thismodel->firstbrush[i].colbrushf, model->brushq3.data_thismodel->firstbrush[i].colbrushf);
+                       if (mod_q3bsp_curves_collisions.integer)
+                       {
+                               for (i = 0;i < model->brushq3.data_thismodel->numfaces;i++)
+                               {
+                                       face = model->brushq3.data_thismodel->firstface + i;
+                                       if (face->collisions)
+                                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, face->num_triangles, face->data_element3i, face->data_vertex3f, face->texture->supercontents, segmentmins, segmentmaxs);
+                               }
+                       }
+               }
+               else
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model->brushq3.data_nodes, thisbrush_start, thisbrush_end, ++markframe, segmentmins, segmentmaxs);
+       }
 }
 
 
@@ -4329,29 +4359,109 @@ loc0:
        return false;
 }
 
-int Mod_Q3BSP_BoxTouchingPVS(model_t *model, const qbyte *pvs, const vec3_t mins, const vec3_t maxs)
+static int Mod_Q3BSP_BoxTouchingPVS(model_t *model, const qbyte *pvs, const vec3_t mins, const vec3_t maxs)
 {
        return Mod_Q3BSP_BoxTouchingPVS_RecursiveBSPNode(model, model->brushq3.data_nodes, pvs, mins, maxs);
 }
 
-int Mod_Q3BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbufferlength)
+//Returns PVS data for a given point
+//(note: can return NULL)
+static qbyte *Mod_Q3BSP_GetPVS(model_t *model, const vec3_t p)
 {
-       // FIXME: write this
-       memset(pvsbuffer, 0xFF, pvsbufferlength);
-       return pvsbufferlength;
+       q3mnode_t *node;
+       Mod_CheckLoaded(model);
+       node = model->brushq3.data_nodes;
+       while (node->isnode)
+               node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
+       if (((q3mleaf_t *)node)->clusterindex >= 0)
+               return model->brushq3.data_pvschains + ((q3mleaf_t *)node)->clusterindex * model->brushq3.num_pvschainlength;
+       else
+               return NULL;
+}
+
+static void Mod_Q3BSP_FatPVS_RecursiveBSPNode(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbytes, q3mnode_t *node)
+{
+       int i;
+       float d;
+       qbyte *pvs;
+
+       while (node->isnode)
+       {
+               d = PlaneDiff(org, node->plane);
+               if (d > radius)
+                       node = node->children[0];
+               else if (d < -radius)
+                       node = node->children[1];
+               else
+               {
+                       // go down both sides
+                       Mod_Q3BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]);
+                       node = node->children[1];
+               }
+       }
+       // if this is a leaf with a pvs, accumulate the pvs bits
+       if (((q3mleaf_t *)node)->clusterindex >= 0)
+       {
+               pvs = model->brushq3.data_pvschains + ((q3mleaf_t *)node)->clusterindex * model->brushq3.num_pvschainlength;
+               for (i = 0;i < pvsbytes;i++)
+                       pvsbuffer[i] |= pvs[i];
+       }
+       return;
+}
+
+//Calculates a PVS that is the inclusive or of all leafs within radius pixels
+//of the given point.
+static int Mod_Q3BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbufferlength)
+{
+       int bytes = model->brushq3.num_pvschainlength;
+       bytes = min(bytes, pvsbufferlength);
+       memset(pvsbuffer, 0, bytes);
+       Mod_Q3BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brushq3.data_nodes);
+       return bytes;
+}
+
+
+static int Mod_Q3BSP_SuperContentsFromNativeContents(model_t *model, int nativecontents)
+{
+       int supercontents = 0;
+       if (nativecontents & Q2CONTENTS_SOLID)
+               supercontents |= SUPERCONTENTS_SOLID;
+       if (nativecontents & Q2CONTENTS_WATER)
+               supercontents |= SUPERCONTENTS_WATER;
+       if (nativecontents & Q2CONTENTS_SLIME)
+               supercontents |= SUPERCONTENTS_SLIME;
+       if (nativecontents & Q2CONTENTS_LAVA)
+               supercontents |= SUPERCONTENTS_LAVA;
+       return supercontents;
+}
+
+static int Mod_Q3BSP_NativeContentsFromSuperContents(model_t *model, int supercontents)
+{
+       int nativecontents = 0;
+       if (supercontents & SUPERCONTENTS_SOLID)
+               nativecontents |= Q2CONTENTS_SOLID;
+       if (supercontents & SUPERCONTENTS_WATER)
+               nativecontents |= Q2CONTENTS_WATER;
+       if (supercontents & SUPERCONTENTS_SLIME)
+               nativecontents |= Q2CONTENTS_SLIME;
+       if (supercontents & SUPERCONTENTS_LAVA)
+               nativecontents |= Q2CONTENTS_LAVA;
+       return nativecontents;
 }
 
 //extern void R_Q3BSP_DrawSky(struct entity_render_s *ent);
 extern void R_Q3BSP_Draw(struct entity_render_s *ent);
-//extern void R_Q3BSP_DrawFakeShadow(struct entity_render_s *ent);
-//extern void R_Q3BSP_DrawShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius);
-//extern void R_Q3BSP_DrawLight(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltofilter, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz);
+extern void R_Q3BSP_DrawShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius);
+extern void R_Q3BSP_DrawLight(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativeeyeorigin, float lightradius, float *lightcolor, const matrix4x4_t *matrix_modeltofilter, const matrix4x4_t *matrix_modeltoattenuationxyz, const matrix4x4_t *matrix_modeltoattenuationz);
 void Mod_Q3BSP_Load(model_t *mod, void *buffer)
 {
        int i;
        q3dheader_t *header;
+       float corner[3], yawradius, modelradius;
 
        mod->type = mod_brushq3;
+       mod->numframes = 1;
+       mod->numskins = 1;
 
        header = (q3dheader_t *)buffer;
 
@@ -4365,6 +4475,9 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
                R_ResetQuakeSky();
        }
 
+       mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents;
+       mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents;
+       mod->brush.GetPVS = Mod_Q3BSP_GetPVS;
        mod->brush.FatPVS = Mod_Q3BSP_FatPVS;
        mod->brush.BoxTouchingPVS = Mod_Q3BSP_BoxTouchingPVS;
        mod->brush.LightPoint = Mod_Q3BSP_LightPoint;
@@ -4372,9 +4485,8 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
        mod->brush.TraceBox = Mod_Q3BSP_TraceBox;
        //mod->DrawSky = R_Q3BSP_DrawSky;
        mod->Draw = R_Q3BSP_Draw;
-       //mod->DrawFakeShadow = R_Q3BSP_DrawFakeShadow;
-       //mod->DrawShadowVolume = R_Q3BSP_DrawShadowVolume;
-       //mod->DrawLight = R_Q3BSP_DrawLight;
+       mod->DrawShadowVolume = R_Q3BSP_DrawShadowVolume;
+       mod->DrawLight = R_Q3BSP_DrawLight;
 
        mod_base = (qbyte *)header;
 
@@ -4422,6 +4534,23 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
                        mod->mempool = NULL;
                }
                mod->brushq3.data_thismodel = loadmodel->brushq3.data_models + i;
+               mod->brushq3.submodel = i;
+
+               VectorCopy(mod->brushq3.data_thismodel->mins, mod->normalmins);
+               VectorCopy(mod->brushq3.data_thismodel->maxs, mod->normalmaxs);
+               corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0]));
+               corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1]));
+               corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2]));
+               modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]);
+               yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]);
+               mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius;
+               mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius;
+               mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius;
+               mod->yawmins[0] = mod->yawmins[1] = -yawradius;
+               mod->yawmins[2] = mod->normalmins[2];
+               mod->yawmaxs[2] = mod->normalmaxs[2];
+               mod->radius = modelradius;
+               mod->radius2 = modelradius * modelradius;
        }
 }