]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_brush.c
re-enable saturation on sidebyside and stereobuffer (it's harmless there), only keep...
[xonotic/darkplaces.git] / model_brush.c
index f2f9e833a498cdcb5143684d6ef363f74acfe66c..f9def07322ba87fb5527bc9a49274adfdc89d415 100644 (file)
@@ -21,63 +21,109 @@ 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 "polygon.h"
 #include "curves.h"
-
-// note: model_shared.c sets up r_notexture, and r_surf_notexture
-
-qbyte mod_q1bsp_novis[(MAX_MAP_LEAFS + 7)/ 8];
-
-//cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128"};
-cvar_t halflifebsp = {0, "halflifebsp", "0"};
-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 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", "0"};
+#include "wad.h"
+
+
+//cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128", "how large water polygons should be (smaller values produce more polygons which give better warping effects)"};
+cvar_t r_novis = {0, "r_novis", "0", "draws whole level, see also sv_cullentities_pvs 0"};
+cvar_t r_picmipworld = {CVAR_SAVE, "r_picmipworld", "1", "whether gl_picmip shall apply to world textures too"};
+cvar_t r_nosurftextures = {0, "r_nosurftextures", "0", "pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case)"};
+cvar_t r_subdivisions_tolerance = {0, "r_subdivisions_tolerance", "4", "maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality)"};
+cvar_t r_subdivisions_mintess = {0, "r_subdivisions_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"};
+cvar_t r_subdivisions_maxtess = {0, "r_subdivisions_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"};
+cvar_t r_subdivisions_maxvertices = {0, "r_subdivisions_maxvertices", "65536", "maximum vertices allowed per subdivided curve"};
+cvar_t r_subdivisions_collision_tolerance = {0, "r_subdivisions_collision_tolerance", "15", "maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering)"};
+cvar_t r_subdivisions_collision_mintess = {0, "r_subdivisions_collision_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"};
+cvar_t r_subdivisions_collision_maxtess = {0, "r_subdivisions_collision_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"};
+cvar_t r_subdivisions_collision_maxvertices = {0, "r_subdivisions_collision_maxvertices", "4225", "maximum vertices allowed per subdivided curve"};
+cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1", "enables collisions with curves (SLOW)"};
+cvar_t mod_q3bsp_curves_collisions_stride = {0, "mod_q3bsp_curves_collisions_stride", "16", "collisions against curves: optimize performance by doing a combined collision check for this triangle amount first"};
+cvar_t mod_q3bsp_curves_stride = {0, "mod_q3bsp_curves_stride", "16", "particle effect collisions against curves: optimize performance by doing a combined collision check for this triangle amount first"};
+cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1", "whether to use optimized traceline code for line traces (as opposed to tracebox code)"};
+cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "selects different tracebrush bsp recursion algorithms (for debugging purposes only)"};
+cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."};
+cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"};
+
+static texture_t mod_q1bsp_texture_solid;
+static texture_t mod_q1bsp_texture_sky;
+static texture_t mod_q1bsp_texture_lava;
+static texture_t mod_q1bsp_texture_slime;
+static texture_t mod_q1bsp_texture_water;
 
 void Mod_BrushInit(void)
 {
 //     Cvar_RegisterVariable(&r_subdivide_size);
-       Cvar_RegisterVariable(&halflifebsp);
        Cvar_RegisterVariable(&r_novis);
-       Cvar_RegisterVariable(&r_miplightmaps);
-       Cvar_RegisterVariable(&r_lightmaprgba);
+       Cvar_RegisterVariable(&r_picmipworld);
        Cvar_RegisterVariable(&r_nosurftextures);
-       Cvar_RegisterVariable(&mod_q3bsp_curves_subdivide_level);
+       Cvar_RegisterVariable(&r_subdivisions_tolerance);
+       Cvar_RegisterVariable(&r_subdivisions_mintess);
+       Cvar_RegisterVariable(&r_subdivisions_maxtess);
+       Cvar_RegisterVariable(&r_subdivisions_maxvertices);
+       Cvar_RegisterVariable(&r_subdivisions_collision_tolerance);
+       Cvar_RegisterVariable(&r_subdivisions_collision_mintess);
+       Cvar_RegisterVariable(&r_subdivisions_collision_maxtess);
+       Cvar_RegisterVariable(&r_subdivisions_collision_maxvertices);
        Cvar_RegisterVariable(&mod_q3bsp_curves_collisions);
+       Cvar_RegisterVariable(&mod_q3bsp_curves_collisions_stride);
+       Cvar_RegisterVariable(&mod_q3bsp_curves_stride);
        Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline);
-       memset(mod_q1bsp_novis, 0xff, sizeof(mod_q1bsp_novis));
+       Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush);
+       Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower);
+       Cvar_RegisterVariable(&mod_q3bsp_nolightmaps);
+
+       memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid));
+       strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name));
+       mod_q1bsp_texture_solid.surfaceflags = 0;
+       mod_q1bsp_texture_solid.supercontents = SUPERCONTENTS_SOLID;
+
+       mod_q1bsp_texture_sky = mod_q1bsp_texture_solid;
+       strlcpy(mod_q1bsp_texture_sky.name, "sky", sizeof(mod_q1bsp_texture_sky.name));
+       mod_q1bsp_texture_sky.surfaceflags = Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT | Q3SURFACEFLAG_NOMARKS | Q3SURFACEFLAG_NODLIGHT | Q3SURFACEFLAG_NOLIGHTMAP;
+       mod_q1bsp_texture_sky.supercontents = SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP;
+
+       mod_q1bsp_texture_lava = mod_q1bsp_texture_solid;
+       strlcpy(mod_q1bsp_texture_lava.name, "*lava", sizeof(mod_q1bsp_texture_lava.name));
+       mod_q1bsp_texture_lava.surfaceflags = Q3SURFACEFLAG_NOMARKS;
+       mod_q1bsp_texture_lava.supercontents = SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP;
+
+       mod_q1bsp_texture_slime = mod_q1bsp_texture_solid;
+       strlcpy(mod_q1bsp_texture_slime.name, "*slime", sizeof(mod_q1bsp_texture_slime.name));
+       mod_q1bsp_texture_slime.surfaceflags = Q3SURFACEFLAG_NOMARKS;
+       mod_q1bsp_texture_slime.supercontents = SUPERCONTENTS_SLIME;
+
+       mod_q1bsp_texture_water = mod_q1bsp_texture_solid;
+       strlcpy(mod_q1bsp_texture_water.name, "*water", sizeof(mod_q1bsp_texture_water.name));
+       mod_q1bsp_texture_water.surfaceflags = Q3SURFACEFLAG_NOMARKS;
+       mod_q1bsp_texture_water.supercontents = SUPERCONTENTS_WATER;
 }
 
-static mleaf_t *Mod_Q1BSP_PointInLeaf(model_t *model, const vec3_t p)
+static mleaf_t *Mod_Q1BSP_PointInLeaf(dp_model_t *model, const vec3_t p)
 {
        mnode_t *node;
 
        if (model == NULL)
                return NULL;
 
-       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 = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
+       while (node->plane)
                node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
 
        return (mleaf_t *)node;
 }
 
-static void Mod_Q1BSP_AmbientSoundLevelsForPoint(model_t *model, const vec3_t p, qbyte *out, int outsize)
+static void Mod_Q1BSP_AmbientSoundLevelsForPoint(dp_model_t *model, const vec3_t p, unsigned char *out, int outsize)
 {
        int i;
        mleaf_t *leaf;
        leaf = Mod_Q1BSP_PointInLeaf(model, p);
        if (leaf)
        {
-               i = min(outsize, (int)sizeof(leaf->ambient_sound_level));;
+               i = min(outsize, (int)sizeof(leaf->ambient_sound_level));
                if (i)
                {
                        memcpy(out, leaf->ambient_sound_level, i);
@@ -89,193 +135,374 @@ static void Mod_Q1BSP_AmbientSoundLevelsForPoint(model_t *model, const vec3_t p,
                memset(out, 0, outsize);
 }
 
-
-static int Mod_Q1BSP_BoxTouchingPVS_RecursiveBSPNode(const model_t *model, const mnode_t *node, const qbyte *pvs, const vec3_t mins, const vec3_t maxs)
+static int Mod_Q1BSP_FindBoxClusters(dp_model_t *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist)
 {
-       int leafnum;
-loc0:
-       if (node->contents < 0)
+       int numclusters = 0;
+       int nodestackindex = 0;
+       mnode_t *node, *nodestack[1024];
+       if (!model->brush.num_pvsclusters)
+               return -1;
+       node = model->brush.data_nodes;
+       for (;;)
        {
-               // leaf
-               if (node->contents == CONTENTS_SOLID)
-                       return false;
-               leafnum = (mleaf_t *)node - model->brushq1.leafs - 1;
-               return pvs[leafnum >> 3] & (1 << (leafnum & 7));
+#if 1
+               if (node->plane)
+               {
+                       // node - recurse down the BSP tree
+                       int sides = BoxOnPlaneSide(mins, maxs, node->plane);
+                       if (sides < 3)
+                       {
+                               if (sides == 0)
+                                       return -1; // ERROR: NAN bounding box!
+                               // box is on one side of plane, take that path
+                               node = node->children[sides-1];
+                       }
+                       else
+                       {
+                               // box crosses plane, take one path and remember the other
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                       }
+                       continue;
+               }
+               else
+               {
+                       // leaf - add clusterindex to list
+                       if (numclusters < maxclusters)
+                               clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex;
+                       numclusters++;
+               }
+#else
+               if (BoxesOverlap(mins, maxs, node->mins, node->maxs))
+               {
+                       if (node->plane)
+                       {
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                               continue;
+                       }
+                       else
+                       {
+                               // leaf - add clusterindex to list
+                               if (numclusters < maxclusters)
+                                       clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex;
+                               numclusters++;
+                       }
+               }
+#endif
+               // try another path we didn't take earlier
+               if (nodestackindex == 0)
+                       break;
+               node = nodestack[--nodestackindex];
        }
+       // return number of clusters found (even if more than the maxclusters)
+       return numclusters;
+}
 
-       // node - recurse down the BSP tree
-       switch (BoxOnPlaneSide(mins, maxs, node->plane))
+static int Mod_Q1BSP_BoxTouchingPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs)
+{
+       int nodestackindex = 0;
+       mnode_t *node, *nodestack[1024];
+       if (!model->brush.num_pvsclusters)
+               return true;
+       node = model->brush.data_nodes;
+       for (;;)
        {
-       case 1: // front
-               node = node->children[0];
-               goto loc0;
-       case 2: // back
-               node = node->children[1];
-               goto loc0;
-       default: // crossing
-               if (node->children[0]->contents != CONTENTS_SOLID)
-                       if (Mod_Q1BSP_BoxTouchingPVS_RecursiveBSPNode(model, node->children[0], pvs, mins, maxs))
+#if 1
+               if (node->plane)
+               {
+                       // node - recurse down the BSP tree
+                       int sides = BoxOnPlaneSide(mins, maxs, node->plane);
+                       if (sides < 3)
+                       {
+                               if (sides == 0)
+                                       return -1; // ERROR: NAN bounding box!
+                               // box is on one side of plane, take that path
+                               node = node->children[sides-1];
+                       }
+                       else
+                       {
+                               // box crosses plane, take one path and remember the other
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                       }
+                       continue;
+               }
+               else
+               {
+                       // leaf - check cluster bit
+                       int clusterindex = ((mleaf_t *)node)->clusterindex;
+                       if (CHECKPVSBIT(pvs, clusterindex))
+                       {
+                               // it is visible, return immediately with the news
                                return true;
-               node = node->children[1];
-               goto loc0;
+                       }
+               }
+#else
+               if (BoxesOverlap(mins, maxs, node->mins, node->maxs))
+               {
+                       if (node->plane)
+                       {
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                               continue;
+                       }
+                       else
+                       {
+                               // leaf - check cluster bit
+                               int clusterindex = ((mleaf_t *)node)->clusterindex;
+                               if (CHECKPVSBIT(pvs, clusterindex))
+                               {
+                                       // it is visible, return immediately with the news
+                                       return true;
+                               }
+                       }
+               }
+#endif
+               // nothing to see here, try another path we didn't take earlier
+               if (nodestackindex == 0)
+                       break;
+               node = nodestack[--nodestackindex];
        }
-       // never reached
+       // it is not visible
        return false;
 }
 
-int Mod_Q1BSP_BoxTouchingPVS(model_t *model, const qbyte *pvs, const vec3_t mins, const vec3_t maxs)
+static int Mod_Q1BSP_BoxTouchingLeafPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs)
 {
-       return Mod_Q1BSP_BoxTouchingPVS_RecursiveBSPNode(model, model->brushq1.nodes + model->brushq1.hulls[0].firstclipnode, pvs, mins, maxs);
+       int nodestackindex = 0;
+       mnode_t *node, *nodestack[1024];
+       if (!model->brush.num_leafs)
+               return true;
+       node = model->brush.data_nodes;
+       for (;;)
+       {
+#if 1
+               if (node->plane)
+               {
+                       // node - recurse down the BSP tree
+                       int sides = BoxOnPlaneSide(mins, maxs, node->plane);
+                       if (sides < 3)
+                       {
+                               if (sides == 0)
+                                       return -1; // ERROR: NAN bounding box!
+                               // box is on one side of plane, take that path
+                               node = node->children[sides-1];
+                       }
+                       else
+                       {
+                               // box crosses plane, take one path and remember the other
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                       }
+                       continue;
+               }
+               else
+               {
+                       // leaf - check cluster bit
+                       int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs;
+                       if (CHECKPVSBIT(pvs, clusterindex))
+                       {
+                               // it is visible, return immediately with the news
+                               return true;
+                       }
+               }
+#else
+               if (BoxesOverlap(mins, maxs, node->mins, node->maxs))
+               {
+                       if (node->plane)
+                       {
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                               continue;
+                       }
+                       else
+                       {
+                               // leaf - check cluster bit
+                               int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs;
+                               if (CHECKPVSBIT(pvs, clusterindex))
+                               {
+                                       // it is visible, return immediately with the news
+                                       return true;
+                               }
+                       }
+               }
+#endif
+               // nothing to see here, try another path we didn't take earlier
+               if (nodestackindex == 0)
+                       break;
+               node = nodestack[--nodestackindex];
+       }
+       // it is not visible
+       return false;
 }
 
-/*
-static int Mod_Q1BSP_PointContents(model_t *model, const vec3_t p)
+static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs)
 {
-       mnode_t *node;
-
-       if (model == NULL)
-               return CONTENTS_EMPTY;
-
-       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)->contents;
+       int nodestackindex = 0;
+       mnode_t *node, *nodestack[1024];
+       if (!model->brush.num_leafs)
+               return true;
+       node = model->brush.data_nodes;
+       for (;;)
+       {
+#if 1
+               if (node->plane)
+               {
+                       // node - recurse down the BSP tree
+                       int sides = BoxOnPlaneSide(mins, maxs, node->plane);
+                       if (sides < 3)
+                       {
+                               if (sides == 0)
+                                       return -1; // ERROR: NAN bounding box!
+                               // box is on one side of plane, take that path
+                               node = node->children[sides-1];
+                       }
+                       else
+                       {
+                               // box crosses plane, take one path and remember the other
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                       }
+                       continue;
+               }
+               else
+               {
+                       // leaf - check if it is visible
+                       if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs])
+                       {
+                               // it is visible, return immediately with the news
+                               return true;
+                       }
+               }
+#else
+               if (BoxesOverlap(mins, maxs, node->mins, node->maxs))
+               {
+                       if (node->plane)
+                       {
+                               if (nodestackindex < 1024)
+                                       nodestack[nodestackindex++] = node->children[0];
+                               node = node->children[1];
+                               continue;
+                       }
+                       else
+                       {
+                               // leaf - check if it is visible
+                               if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs])
+                               {
+                                       // it is visible, return immediately with the news
+                                       return true;
+                               }
+                       }
+               }
+#endif
+               // nothing to see here, try another path we didn't take earlier
+               if (nodestackindex == 0)
+                       break;
+               node = nodestack[--nodestackindex];
+       }
+       // it is not visible
+       return false;
 }
-*/
 
 typedef struct findnonsolidlocationinfo_s
 {
        vec3_t center;
+       vec3_t absmin, absmax;
        vec_t radius;
        vec3_t nudge;
        vec_t bestdist;
-       model_t *model;
+       dp_model_t *model;
 }
 findnonsolidlocationinfo_t;
 
-#if 0
-extern cvar_t samelevel;
-#endif
-static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf)
+static void Mod_Q1BSP_FindNonSolidLocation_r_Triangle(findnonsolidlocationinfo_t *info, msurface_t *surface, int k)
 {
-       int i, surfnum, k, *tri, *mark;
+       int i, *tri;
        float dist, f, vert[3][3], edge[3][3], facenormal[3], edgenormal[3][3], point[3];
-#if 0
-       float surfnormal[3];
-#endif
-       msurface_t *surf;
-       for (surfnum = 0, mark = leaf->firstmarksurface;surfnum < leaf->nummarksurfaces;surfnum++, mark++)
+
+       tri = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle) + k * 3;
+       VectorCopy((info->model->surfmesh.data_vertex3f + tri[0] * 3), vert[0]);
+       VectorCopy((info->model->surfmesh.data_vertex3f + tri[1] * 3), vert[1]);
+       VectorCopy((info->model->surfmesh.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])
        {
-               surf = info->model->brushq1.surfaces + *mark;
-               if (surf->flags & SURF_SOLIDCLIP)
+               VectorNormalize(facenormal);
+               f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+               if (f <= info->bestdist && f >= -info->bestdist)
                {
-#if 0
-                       VectorCopy(surf->plane->normal, surfnormal);
-                       if (surf->flags & SURF_PLANEBACK)
-                               VectorNegate(surfnormal, surfnormal);
-#endif
-                       for (k = 0;k < surf->mesh.num_triangles;k++)
+                       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]);
+                       // 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]))
                        {
-                               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])
+                               // we got lucky, the center is within the face
+                               dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+                               if (dist < 0)
                                {
-                                       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)
+                                       dist = -dist;
+                                       if (info->bestdist > dist)
                                        {
-                                               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]);
-                                               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]))
+                                               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++)
+                               {
+                                       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)
                                                {
-                                                       // 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);
-                                                               }
-                                                       }
+                                                       info->bestdist = dist;
+                                                       VectorScale(point, (info->radius / dist), info->nudge);
                                                }
-                                               else
+                                               // 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)
                                                {
-                                                       // 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);
-                                                                       }
-                                                               }
-                                                       }
+                                                       info->bestdist = dist;
+                                                       VectorScale(point, (info->radius / dist), info->nudge);
                                                }
                                        }
                                }
@@ -284,14 +511,47 @@ static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *in
        }
 }
 
-static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, mnode_t *node)
+static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf)
 {
-       if (node->contents)
+       int surfacenum, k, *mark;
+       msurface_t *surface;
+       for (surfacenum = 0, mark = leaf->firstleafsurface;surfacenum < leaf->numleafsurfaces;surfacenum++, mark++)
        {
-               if (((mleaf_t *)node)->nummarksurfaces)
-                       Mod_Q1BSP_FindNonSolidLocation_r_Leaf(info, (mleaf_t *)node);
+               surface = info->model->data_surfaces + *mark;
+               if (surface->texture->supercontents & SUPERCONTENTS_SOLID)
+               {
+                       if(surface->num_bboxstride)
+                       {
+                               int i, cnt, tri;
+                               cnt = (surface->num_triangles + surface->num_bboxstride - 1) / surface->num_bboxstride;
+                               for(i = 0; i < cnt; ++i)
+                               {
+                                       if(BoxesOverlap(surface->data_bbox6f + i * 6, surface->data_bbox6f + i * 6 + 3, info->absmin, info->absmax))
+                                       {
+                                               for(k = 0; k < surface->num_bboxstride; ++k)
+                                               {
+                                                       tri = i * surface->num_bboxstride + k;
+                                                       if(tri >= surface->num_triangles)
+                                                               break;
+                                                       Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, tri);
+                                               }
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               for (k = 0;k < surface->num_triangles;k++)
+                               {
+                                       Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, k);
+                               }
+                       }
+               }
        }
-       else
+}
+
+static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, mnode_t *node)
+{
+       if (node->plane)
        {
                float f = PlaneDiff(info->center, node->plane);
                if (f >= -info->bestdist)
@@ -299,9 +559,14 @@ static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, m
                if (f <= info->bestdist)
                        Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[1]);
        }
+       else
+       {
+               if (((mleaf_t *)node)->numleafsurfaces)
+                       Mod_Q1BSP_FindNonSolidLocation_r_Leaf(info, (mleaf_t *)node);
+       }
 }
 
-static void Mod_Q1BSP_FindNonSolidLocation(model_t *model, const vec3_t in, vec3_t out, float radius)
+static void Mod_Q1BSP_FindNonSolidLocation(dp_model_t *model, const vec3_t in, vec3_t out, float radius)
 {
        int i;
        findnonsolidlocationinfo_t info;
@@ -318,36 +583,44 @@ static void Mod_Q1BSP_FindNonSolidLocation(model_t *model, const vec3_t in, vec3
        {
                VectorClear(info.nudge);
                info.bestdist = radius;
-               Mod_Q1BSP_FindNonSolidLocation_r(&info, model->brushq1.nodes + model->brushq1.hulls[0].firstclipnode);
+               VectorCopy(info.center, info.absmin);
+               VectorCopy(info.center, info.absmax);
+               info.absmin[0] -= info.radius + 1;
+               info.absmin[1] -= info.radius + 1;
+               info.absmin[2] -= info.radius + 1;
+               info.absmax[0] += info.radius + 1;
+               info.absmax[1] += info.radius + 1;
+               info.absmax[2] += info.radius + 1;
+               Mod_Q1BSP_FindNonSolidLocation_r(&info, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode);
                VectorAdd(info.center, info.nudge, info.center);
        }
        while (info.bestdist < radius && ++i < 10);
        VectorCopy(info.center, out);
 }
 
-int Mod_Q1BSP_SuperContentsFromNativeContents(model_t *model, int nativecontents)
+int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents)
 {
        switch(nativecontents)
        {
                case CONTENTS_EMPTY:
                        return 0;
                case CONTENTS_SOLID:
-                       return SUPERCONTENTS_SOLID;
+                       return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE;
                case CONTENTS_WATER:
                        return SUPERCONTENTS_WATER;
                case CONTENTS_SLIME:
                        return SUPERCONTENTS_SLIME;
                case CONTENTS_LAVA:
-                       return SUPERCONTENTS_LAVA;
+                       return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP;
                case CONTENTS_SKY:
-                       return SUPERCONTENTS_SKY;
+                       return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
        }
        return 0;
 }
 
-int Mod_Q1BSP_NativeContentsFromSuperContents(model_t *model, int supercontents)
+int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents)
 {
-       if (supercontents & SUPERCONTENTS_SOLID)
+       if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))
                return CONTENTS_SOLID;
        if (supercontents & SUPERCONTENTS_SKY)
                return CONTENTS_SKY;
@@ -360,7 +633,7 @@ int Mod_Q1BSP_NativeContentsFromSuperContents(model_t *model, int supercontents)
        return CONTENTS_EMPTY;
 }
 
-typedef struct
+typedef struct RecursiveHullCheckTraceInfo_s
 {
        // the hull we're tracing through
        const hull_t *hull;
@@ -382,6 +655,7 @@ RecursiveHullCheckTraceInfo_t;
 #define HULLCHECKSTATE_SOLID 1
 #define HULLCHECKSTATE_DONE 2
 
+extern cvar_t collision_prefernudgedfraction;
 static int Mod_Q1BSP_RecursiveHullCheck(RecursiveHullCheckTraceInfo_t *t, int num, double p1f, double p2f, double p1[3], double p2[3])
 {
        // status variables, these don't need to be saved on the stack when
@@ -392,7 +666,7 @@ static int Mod_Q1BSP_RecursiveHullCheck(RecursiveHullCheckTraceInfo_t *t, int nu
        double t1, t2;
 
        // variables that need to be stored on the stack when recursing
-       dclipnode_t *node;
+       mclipnode_t *node;
        int side;
        double midf, mid[3];
 
@@ -411,16 +685,34 @@ loc0:
                        t->trace->inwater = true;
                if (num == 0)
                        t->trace->inopen = true;
+               if (num & SUPERCONTENTS_SOLID)
+                       t->trace->hittexture = &mod_q1bsp_texture_solid;
+               else if (num & SUPERCONTENTS_SKY)
+                       t->trace->hittexture = &mod_q1bsp_texture_sky;
+               else if (num & SUPERCONTENTS_LAVA)
+                       t->trace->hittexture = &mod_q1bsp_texture_lava;
+               else if (num & SUPERCONTENTS_SLIME)
+                       t->trace->hittexture = &mod_q1bsp_texture_slime;
+               else
+                       t->trace->hittexture = &mod_q1bsp_texture_water;
+               t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags;
+               t->trace->hitsupercontents = num;
                if (num & t->trace->hitsupercontentsmask)
                {
                        // if the first leaf is solid, set startsolid
                        if (t->trace->allsolid)
                                t->trace->startsolid = true;
+#if COLLISIONPARANOID >= 3
+                       Con_Print("S");
+#endif
                        return HULLCHECKSTATE_SOLID;
                }
                else
                {
                        t->trace->allsolid = false;
+#if COLLISIONPARANOID >= 3
+                       Con_Print("E");
+#endif
                        return HULLCHECKSTATE_EMPTY;
                }
        }
@@ -444,6 +736,9 @@ loc0:
        {
                if (t2 < 0)
                {
+#if COLLISIONPARANOID >= 3
+                       Con_Print("<");
+#endif
                        num = node->children[1];
                        goto loc0;
                }
@@ -453,6 +748,9 @@ loc0:
        {
                if (t2 >= 0)
                {
+#if COLLISIONPARANOID >= 3
+                       Con_Print(">");
+#endif
                        num = node->children[0];
                        goto loc0;
                }
@@ -461,6 +759,9 @@ loc0:
 
        // the line intersects, find intersection point
        // LordHavoc: this uses the original trace for maximum accuracy
+#if COLLISIONPARANOID >= 3
+       Con_Print("M");
+#endif
        if (plane->type < 3)
        {
                t1 = t->start[plane->type] - plane->dist;
@@ -499,17 +800,58 @@ loc0:
                VectorCopy (plane->normal, t->trace->plane.normal);
        }
 
-       // bias away from surface a bit
-       t1 = DotProduct(t->trace->plane.normal, t->start) - (t->trace->plane.dist + DIST_EPSILON);
-       t2 = DotProduct(t->trace->plane.normal, t->end) - (t->trace->plane.dist + DIST_EPSILON);
-
+       // calculate the true fraction
+       t1 = DotProduct(t->trace->plane.normal, t->start) - t->trace->plane.dist;
+       t2 = DotProduct(t->trace->plane.normal, t->end) - t->trace->plane.dist;
        midf = t1 / (t1 - t2);
-       t->trace->fraction = bound(0.0f, midf, 1.0);
+       t->trace->realfraction = bound(0, midf, 1);
+
+       // calculate the return fraction which is nudged off the surface a bit
+       midf = (t1 - DIST_EPSILON) / (t1 - t2);
+       t->trace->fraction = bound(0, midf, 1);
+
+       if (collision_prefernudgedfraction.integer)
+               t->trace->realfraction = t->trace->fraction;
 
+#if COLLISIONPARANOID >= 3
+       Con_Print("D");
+#endif
        return HULLCHECKSTATE_DONE;
 }
 
-static void Mod_Q1BSP_TraceBox(struct model_s *model, int frame, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs, int hitsupercontentsmask)
+//#if COLLISIONPARANOID < 2
+static int Mod_Q1BSP_RecursiveHullCheckPoint(RecursiveHullCheckTraceInfo_t *t, int num)
+{
+       mplane_t *plane;
+       mclipnode_t *nodes = t->hull->clipnodes;
+       mplane_t *planes = t->hull->planes;
+       vec3_t point;
+       VectorCopy(t->start, point);
+       while (num >= 0)
+       {
+               plane = planes + nodes[num].planenum;
+               num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist];
+       }
+       num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num);
+       t->trace->startsupercontents |= num;
+       if (num & SUPERCONTENTS_LIQUIDSMASK)
+               t->trace->inwater = true;
+       if (num == 0)
+               t->trace->inopen = true;
+       if (num & t->trace->hitsupercontentsmask)
+       {
+               t->trace->allsolid = t->trace->startsolid = true;
+               return HULLCHECKSTATE_SOLID;
+       }
+       else
+       {
+               t->trace->allsolid = t->trace->startsolid = false;
+               return HULLCHECKSTATE_EMPTY;
+       }
+}
+//#endif
+
+static void Mod_Q1BSP_TraceBox(struct model_s *model, int frame, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask)
 {
        // this function currently only supports same size start and end
        double boxsize[3];
@@ -520,13 +862,16 @@ static void Mod_Q1BSP_TraceBox(struct model_s *model, int frame, trace_t *trace,
        rhc.trace = trace;
        rhc.trace->hitsupercontentsmask = hitsupercontentsmask;
        rhc.trace->fraction = 1;
+       rhc.trace->realfraction = 1;
        rhc.trace->allsolid = true;
-       VectorSubtract(boxstartmaxs, boxstartmins, boxsize);
+       VectorSubtract(boxmaxs, boxmins, boxsize);
        if (boxsize[0] < 3)
                rhc.hull = &model->brushq1.hulls[0]; // 0x0x0
        else if (model->brush.ishlbsp)
        {
-               if (boxsize[0] <= 32)
+               // LordHavoc: this has to have a minor tolerance (the .1) because of
+               // minor float precision errors from the box being transformed around
+               if (boxsize[0] < 32.1)
                {
                        if (boxsize[2] < 54) // pick the nearest of 36 or 72
                                rhc.hull = &model->brushq1.hulls[3]; // 32x32x36
@@ -538,25 +883,243 @@ static void Mod_Q1BSP_TraceBox(struct model_s *model, int frame, trace_t *trace,
        }
        else
        {
-               if (boxsize[0] <= 32)
+               // LordHavoc: this has to have a minor tolerance (the .1) because of
+               // minor float precision errors from the box being transformed around
+               if (boxsize[0] < 32.1)
                        rhc.hull = &model->brushq1.hulls[1]; // 32x32x56
                else
                        rhc.hull = &model->brushq1.hulls[2]; // 64x64x88
        }
-       VectorSubtract(boxstartmins, rhc.hull->clip_mins, rhc.start);
-       VectorSubtract(boxendmins, rhc.hull->clip_mins, rhc.end);
+       VectorMAMAM(1, start, 1, boxmins, -1, rhc.hull->clip_mins, rhc.start);
+       VectorMAMAM(1, end, 1, boxmins, -1, rhc.hull->clip_mins, rhc.end);
        VectorSubtract(rhc.end, rhc.start, rhc.dist);
+#if COLLISIONPARANOID >= 2
+       Con_Printf("t(%f %f %f,%f %f %f,%i %f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2], rhc.hull - model->brushq1.hulls, rhc.hull->clip_mins[0], rhc.hull->clip_mins[1], rhc.hull->clip_mins[2]);
        Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
+       {
+
+               double test[3];
+               trace_t testtrace;
+               VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test);
+               memset(&testtrace, 0, sizeof(trace_t));
+               rhc.trace = &testtrace;
+               rhc.trace->hitsupercontentsmask = hitsupercontentsmask;
+               rhc.trace->fraction = 1;
+               rhc.trace->realfraction = 1;
+               rhc.trace->allsolid = true;
+               VectorCopy(test, rhc.start);
+               VectorCopy(test, rhc.end);
+               VectorClear(rhc.dist);
+               Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode);
+               //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test);
+               if (!trace->startsolid && testtrace.startsolid)
+                       Con_Printf(" - ended in solid!\n");
+       }
+       Con_Print("\n");
+#else
+       if (VectorLength2(rhc.dist))
+               Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
+       else
+               Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode);
+#endif
+}
+
+static int Mod_Q1BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point)
+{
+       int num = model->brushq1.hulls[0].firstclipnode;
+       mplane_t *plane;
+       mclipnode_t *nodes = model->brushq1.hulls[0].clipnodes;
+       mplane_t *planes = model->brushq1.hulls[0].planes;
+       while (num >= 0)
+       {
+               plane = planes + nodes[num].planenum;
+               num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist];
+       }
+       return Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num);
 }
 
-static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz)
+void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, texture_t *boxtexture)
 {
-       int side, distz = endz - startz;
+#if 1
+       colbrushf_t cbox;
+       colplanef_t cbox_planes[6];
+       cbox.supercontents = boxsupercontents;
+       cbox.numplanes = 6;
+       cbox.numpoints = 0;
+       cbox.numtriangles = 0;
+       cbox.planes = cbox_planes;
+       cbox.points = NULL;
+       cbox.elements = NULL;
+       cbox.markframe = 0;
+       cbox.mins[0] = 0;
+       cbox.mins[1] = 0;
+       cbox.mins[2] = 0;
+       cbox.maxs[0] = 0;
+       cbox.maxs[1] = 0;
+       cbox.maxs[2] = 0;
+       cbox_planes[0].normal[0] =  1;cbox_planes[0].normal[1] =  0;cbox_planes[0].normal[2] =  0;cbox_planes[0].dist = cmaxs[0] - mins[0];
+       cbox_planes[1].normal[0] = -1;cbox_planes[1].normal[1] =  0;cbox_planes[1].normal[2] =  0;cbox_planes[1].dist = maxs[0] - cmins[0];
+       cbox_planes[2].normal[0] =  0;cbox_planes[2].normal[1] =  1;cbox_planes[2].normal[2] =  0;cbox_planes[2].dist = cmaxs[1] - mins[1];
+       cbox_planes[3].normal[0] =  0;cbox_planes[3].normal[1] = -1;cbox_planes[3].normal[2] =  0;cbox_planes[3].dist = maxs[1] - cmins[1];
+       cbox_planes[4].normal[0] =  0;cbox_planes[4].normal[1] =  0;cbox_planes[4].normal[2] =  1;cbox_planes[4].dist = cmaxs[2] - mins[2];
+       cbox_planes[5].normal[0] =  0;cbox_planes[5].normal[1] =  0;cbox_planes[5].normal[2] = -1;cbox_planes[5].dist = maxs[2] - cmins[2];
+       cbox_planes[0].q3surfaceflags = boxq3surfaceflags;cbox_planes[0].texture = boxtexture;
+       cbox_planes[1].q3surfaceflags = boxq3surfaceflags;cbox_planes[1].texture = boxtexture;
+       cbox_planes[2].q3surfaceflags = boxq3surfaceflags;cbox_planes[2].texture = boxtexture;
+       cbox_planes[3].q3surfaceflags = boxq3surfaceflags;cbox_planes[3].texture = boxtexture;
+       cbox_planes[4].q3surfaceflags = boxq3surfaceflags;cbox_planes[4].texture = boxtexture;
+       cbox_planes[5].q3surfaceflags = boxq3surfaceflags;cbox_planes[5].texture = boxtexture;
+       memset(trace, 0, sizeof(trace_t));
+       trace->hitsupercontentsmask = hitsupercontentsmask;
+       trace->fraction = 1;
+       trace->realfraction = 1;
+       Collision_TraceLineBrushFloat(trace, start, end, &cbox, &cbox);
+#else
+       RecursiveHullCheckTraceInfo_t rhc;
+       static hull_t box_hull;
+       static mclipnode_t box_clipnodes[6];
+       static mplane_t box_planes[6];
+       // fill in a default trace
+       memset(&rhc, 0, sizeof(rhc));
+       memset(trace, 0, sizeof(trace_t));
+       //To keep everything totally uniform, bounding boxes are turned into small
+       //BSP trees instead of being compared directly.
+       // create a temp hull from bounding box sizes
+       box_planes[0].dist = cmaxs[0] - mins[0];
+       box_planes[1].dist = cmins[0] - maxs[0];
+       box_planes[2].dist = cmaxs[1] - mins[1];
+       box_planes[3].dist = cmins[1] - maxs[1];
+       box_planes[4].dist = cmaxs[2] - mins[2];
+       box_planes[5].dist = cmins[2] - maxs[2];
+#if COLLISIONPARANOID >= 3
+       Con_Printf("box_planes %f:%f %f:%f %f:%f\ncbox %f %f %f:%f %f %f\nbox %f %f %f:%f %f %f\n", box_planes[0].dist, box_planes[1].dist, box_planes[2].dist, box_planes[3].dist, box_planes[4].dist, box_planes[5].dist, cmins[0], cmins[1], cmins[2], cmaxs[0], cmaxs[1], cmaxs[2], mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]);
+#endif
+
+       if (box_hull.clipnodes == NULL)
+       {
+               int i, side;
+
+               //Set up the planes and clipnodes so that the six floats of a bounding box
+               //can just be stored out and get a proper hull_t structure.
+
+               box_hull.clipnodes = box_clipnodes;
+               box_hull.planes = box_planes;
+               box_hull.firstclipnode = 0;
+               box_hull.lastclipnode = 5;
+
+               for (i = 0;i < 6;i++)
+               {
+                       box_clipnodes[i].planenum = i;
+
+                       side = i&1;
+
+                       box_clipnodes[i].children[side] = CONTENTS_EMPTY;
+                       if (i != 5)
+                               box_clipnodes[i].children[side^1] = i + 1;
+                       else
+                               box_clipnodes[i].children[side^1] = CONTENTS_SOLID;
+
+                       box_planes[i].type = i>>1;
+                       box_planes[i].normal[i>>1] = 1;
+               }
+       }
+
+       // trace a line through the generated clipping hull
+       //rhc.boxsupercontents = boxsupercontents;
+       rhc.hull = &box_hull;
+       rhc.trace = trace;
+       rhc.trace->hitsupercontentsmask = hitsupercontentsmask;
+       rhc.trace->fraction = 1;
+       rhc.trace->realfraction = 1;
+       rhc.trace->allsolid = true;
+       VectorCopy(start, rhc.start);
+       VectorCopy(end, rhc.end);
+       VectorSubtract(rhc.end, rhc.start, rhc.dist);
+       Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end);
+       //VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos);
+       if (rhc.trace->startsupercontents)
+               rhc.trace->startsupercontents = boxsupercontents;
+#endif
+}
+
+static int Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3])
+{
+       double t1, t2;
+       double midf, mid[3];
+       int ret, side;
+
+       // check for empty
+       while (node->plane)
+       {
+               // find the point distances
+               mplane_t *plane = node->plane;
+               if (plane->type < 3)
+               {
+                       t1 = p1[plane->type] - plane->dist;
+                       t2 = p2[plane->type] - plane->dist;
+               }
+               else
+               {
+                       t1 = DotProduct (plane->normal, p1) - plane->dist;
+                       t2 = DotProduct (plane->normal, p2) - plane->dist;
+               }
+
+               if (t1 < 0)
+               {
+                       if (t2 < 0)
+                       {
+                               node = node->children[1];
+                               continue;
+                       }
+                       side = 1;
+               }
+               else
+               {
+                       if (t2 >= 0)
+                       {
+                               node = node->children[0];
+                               continue;
+                       }
+                       side = 0;
+               }
+
+               midf = t1 / (t1 - t2);
+               VectorLerp(p1, midf, p2, mid);
+
+               // recurse both sides, front side first
+               // return 2 if empty is followed by solid (hit something)
+               // do not return 2 if both are solid or both empty,
+               // or if start is solid and end is empty
+               // as these degenerate cases usually indicate the eye is in solid and
+               // should see the target point anyway
+               ret = Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side    ], p1, mid);
+               if (ret != 0)
+                       return ret;
+               ret = Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2);
+               if (ret != 1)
+                       return ret;
+               return 2;
+       }
+       return ((mleaf_t *)node)->clusterindex < 0;
+}
+
+static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
+{
+       // this function currently only supports same size start and end
+       double tracestart[3], traceend[3];
+       VectorCopy(start, tracestart);
+       VectorCopy(end, traceend);
+       return Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend) != 2;
+}
+
+static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(dp_model_t *model, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz)
+{
+       int side;
        float front, back;
-       float mid;
+       float mid, distz = endz - startz;
 
 loc0:
-       if (node->contents < 0)
+       if (!node->plane)
                return false;           // didn't hit anything
 
        switch (node->plane->type)
@@ -593,91 +1156,70 @@ loc0:
        }
 
        // go down front side
-       if (node->children[side]->contents >= 0 && Mod_Q1BSP_LightPoint_RecursiveBSPNode(ambientcolor, diffusecolor, diffusenormal, node->children[side], x, y, startz, mid))
+       if (node->children[side]->plane && Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, node->children[side], x, y, startz, mid))
                return true;    // hit something
        else
        {
                // check for impact on this node
                if (node->numsurfaces)
                {
-                       int i, ds, dt;
-                       msurface_t *surf;
-
-                       surf = cl.worldmodel->brushq1.surfaces + node->firstsurface;
-                       for (i = 0;i < node->numsurfaces;i++, surf++)
+                       int i, dsi, dti, lmwidth, lmheight;
+                       float ds, dt;
+                       msurface_t *surface;
+                       unsigned char *lightmap;
+                       int maps, line3, size3;
+                       float dsfrac;
+                       float dtfrac;
+                       float scale, w, w00, w01, w10, w11;
+
+                       surface = model->data_surfaces + node->firstsurface;
+                       for (i = 0;i < node->numsurfaces;i++, surface++)
                        {
-                               if (!(surf->flags & SURF_LIGHTMAP))
+                               if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo->samples)
                                        continue;       // no lightmaps
 
-                               ds = (int) (x * surf->texinfo->vecs[0][0] + y * surf->texinfo->vecs[0][1] + mid * surf->texinfo->vecs[0][2] + surf->texinfo->vecs[0][3]);
-                               dt = (int) (x * surf->texinfo->vecs[1][0] + y * surf->texinfo->vecs[1][1] + mid * surf->texinfo->vecs[1][2] + surf->texinfo->vecs[1][3]);
-
-                               if (ds < surf->texturemins[0] || dt < surf->texturemins[1])
-                                       continue;
-
-                               ds -= surf->texturemins[0];
-                               dt -= surf->texturemins[1];
+                               // location we want to sample in the lightmap
+                               ds = ((x * surface->lightmapinfo->texinfo->vecs[0][0] + y * surface->lightmapinfo->texinfo->vecs[0][1] + mid * surface->lightmapinfo->texinfo->vecs[0][2] + surface->lightmapinfo->texinfo->vecs[0][3]) - surface->lightmapinfo->texturemins[0]) * 0.0625f;
+                               dt = ((x * surface->lightmapinfo->texinfo->vecs[1][0] + y * surface->lightmapinfo->texinfo->vecs[1][1] + mid * surface->lightmapinfo->texinfo->vecs[1][2] + surface->lightmapinfo->texinfo->vecs[1][3]) - surface->lightmapinfo->texturemins[1]) * 0.0625f;
 
-                               if (ds > surf->extents[0] || dt > surf->extents[1])
-                                       continue;
+                               // check the bounds
+                               dsi = (int)ds;
+                               dti = (int)dt;
+                               lmwidth = ((surface->lightmapinfo->extents[0]>>4)+1);
+                               lmheight = ((surface->lightmapinfo->extents[1]>>4)+1);
 
-                               if (surf->samples)
+                               // is it in bounds?
+                               if (dsi >= 0 && dsi < lmwidth-1 && dti >= 0 && dti < lmheight-1)
                                {
-                                       qbyte *lightmap;
-                                       int maps, line3, size3, dsfrac = ds & 15, dtfrac = dt & 15, scale = 0, r00 = 0, g00 = 0, b00 = 0, r01 = 0, g01 = 0, b01 = 0, r10 = 0, g10 = 0, b10 = 0, r11 = 0, g11 = 0, b11 = 0;
-                                       line3 = ((surf->extents[0]>>4)+1)*3;
-                                       size3 = ((surf->extents[0]>>4)+1) * ((surf->extents[1]>>4)+1)*3; // LordHavoc: *3 for colored lighting
-
-                                       lightmap = surf->samples + ((dt>>4) * ((surf->extents[0]>>4)+1) + (ds>>4))*3; // LordHavoc: *3 for color
-
-                                       for (maps = 0;maps < MAXLIGHTMAPS && surf->styles[maps] != 255;maps++)
+                                       // calculate bilinear interpolation factors
+                                       // and also multiply by fixedpoint conversion factors
+                                       dsfrac = ds - dsi;
+                                       dtfrac = dt - dti;
+                                       w00 = (1 - dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f);
+                                       w01 = (    dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f);
+                                       w10 = (1 - dsfrac) * (    dtfrac) * (1.0f / 32768.0f);
+                                       w11 = (    dsfrac) * (    dtfrac) * (1.0f / 32768.0f);
+
+                                       // values for pointer math
+                                       line3 = lmwidth * 3; // LordHavoc: *3 for colored lighting
+                                       size3 = lmwidth * lmheight * 3; // LordHavoc: *3 for colored lighting
+
+                                       // look up the pixel
+                                       lightmap = surface->lightmapinfo->samples + dti * line3 + dsi*3; // LordHavoc: *3 for colored lighting
+
+                                       // bilinear filter each lightmap style, and sum them
+                                       for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++)
                                        {
-                                               scale = d_lightstylevalue[surf->styles[maps]];
-                                               r00 += lightmap[      0] * scale;g00 += lightmap[      1] * scale;b00 += lightmap[      2] * scale;
-                                               r01 += lightmap[      3] * scale;g01 += lightmap[      4] * scale;b01 += lightmap[      5] * scale;
-                                               r10 += lightmap[line3+0] * scale;g10 += lightmap[line3+1] * scale;b10 += lightmap[line3+2] * scale;
-                                               r11 += lightmap[line3+3] * scale;g11 += lightmap[line3+4] * scale;b11 += lightmap[line3+5] * scale;
+                                               scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]];
+                                               w = w00 * scale;VectorMA(ambientcolor, w, lightmap            , ambientcolor);
+                                               w = w01 * scale;VectorMA(ambientcolor, w, lightmap + 3        , ambientcolor);
+                                               w = w10 * scale;VectorMA(ambientcolor, w, lightmap + line3    , ambientcolor);
+                                               w = w11 * scale;VectorMA(ambientcolor, w, lightmap + line3 + 3, ambientcolor);
                                                lightmap += size3;
                                        }
 
-/*
-LordHavoc: here's the readable version of the interpolation
-code, not quite as easy for the compiler to optimize...
-
-dsfrac is the X position in the lightmap pixel, * 16
-dtfrac is the Y position in the lightmap pixel, * 16
-r00 is top left corner, r01 is top right corner
-r10 is bottom left corner, r11 is bottom right corner
-g and b are the same layout.
-r0 and r1 are the top and bottom intermediate results
-
-first we interpolate the top two points, to get the top
-edge sample
-
-       r0 = (((r01-r00) * dsfrac) >> 4) + r00;
-       g0 = (((g01-g00) * dsfrac) >> 4) + g00;
-       b0 = (((b01-b00) * dsfrac) >> 4) + b00;
-
-then we interpolate the bottom two points, to get the
-bottom edge sample
-
-       r1 = (((r11-r10) * dsfrac) >> 4) + r10;
-       g1 = (((g11-g10) * dsfrac) >> 4) + g10;
-       b1 = (((b11-b10) * dsfrac) >> 4) + b10;
-
-then we interpolate the top and bottom samples to get the
-middle sample (the one which was requested)
-
-       r = (((r1-r0) * dtfrac) >> 4) + r0;
-       g = (((g1-g0) * dtfrac) >> 4) + g0;
-       b = (((b1-b0) * dtfrac) >> 4) + b0;
-*/
-
-                                       ambientcolor[0] += (float) ((((((((r11-r10) * dsfrac) >> 4) + r10)-((((r01-r00) * dsfrac) >> 4) + r00)) * dtfrac) >> 4) + ((((r01-r00) * dsfrac) >> 4) + r00)) * (1.0f / 32768.0f);
-                                       ambientcolor[1] += (float) ((((((((g11-g10) * dsfrac) >> 4) + g10)-((((g01-g00) * dsfrac) >> 4) + g00)) * dtfrac) >> 4) + ((((g01-g00) * dsfrac) >> 4) + g00)) * (1.0f / 32768.0f);
-                                       ambientcolor[2] += (float) ((((((((b11-b10) * dsfrac) >> 4) + b10)-((((b01-b00) * dsfrac) >> 4) + b00)) * dtfrac) >> 4) + ((((b01-b00) * dsfrac) >> 4) + b00)) * (1.0f / 32768.0f);
+                                       return true; // success
                                }
-                               return true; // success
                        }
                }
 
@@ -689,19 +1231,30 @@ middle sample (the one which was requested)
        }
 }
 
-void Mod_Q1BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal)
+void Mod_Q1BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal)
 {
-       Mod_Q1BSP_LightPoint_RecursiveBSPNode(ambientcolor, diffusecolor, diffusenormal, cl.worldmodel->brushq1.nodes + cl.worldmodel->brushq1.hulls[0].firstclipnode, p[0], p[1], p[2], p[2] - 65536);
+       // pretend lighting is coming down from above (due to lack of a lightgrid to know primary lighting direction)
+       VectorSet(diffusenormal, 0, 0, 1);
+
+       if (!model->brushq1.lightdata)
+       {
+               VectorSet(ambientcolor, 1, 1, 1);
+               VectorSet(diffusecolor, 0, 0, 0);
+               return;
+       }
+
+       Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, p[0], p[1], p[2] + 0.125, p[2] - 65536);
 }
 
-static void Mod_Q1BSP_DecompressVis(const qbyte *in, const qbyte *inend, qbyte *out, qbyte *outend)
+static void Mod_Q1BSP_DecompressVis(const unsigned char *in, const unsigned char *inend, unsigned char *out, unsigned char *outend)
 {
        int c;
+       unsigned char *outstart = out;
        while (out < outend)
        {
                if (in == inend)
                {
-                       Con_DPrintf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\"\n", loadmodel->name);
+                       Con_Printf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart));
                        return;
                }
                c = *in++;
@@ -711,14 +1264,14 @@ static void Mod_Q1BSP_DecompressVis(const qbyte *in, const qbyte *inend, qbyte *
                {
                        if (in == inend)
                        {
-                               Con_DPrintf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\"\n", loadmodel->name);
+                               Con_Printf("Mod_Q1BSP_DecompressVis: input underrun (during zero-run) on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart));
                                return;
                        }
                        for (c = *in++;c > 0;c--)
                        {
                                if (out == outend)
                                {
-                                       Con_DPrintf("Mod_Q1BSP_DecompressVis: output overrun on model \"%s\"\n", loadmodel->name);
+                                       Con_Printf("Mod_Q1BSP_DecompressVis: output overrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart));
                                        return;
                                }
                                *out++ = 0;
@@ -727,52 +1280,158 @@ static void Mod_Q1BSP_DecompressVis(const qbyte *in, const qbyte *inend, qbyte *
        }
 }
 
+/*
+=============
+R_Q1BSP_LoadSplitSky
+
+A sky texture is 256*128, with the right side being a masked overlay
+==============
+*/
+void R_Q1BSP_LoadSplitSky (unsigned char *src, int width, int height, int bytesperpixel)
+{
+       int i, j;
+       unsigned solidpixels[128*128], alphapixels[128*128];
+
+       // allocate a texture pool if we need it
+       if (loadmodel->texturepool == NULL && cls.state != ca_dedicated)
+               loadmodel->texturepool = R_AllocTexturePool();
+
+       if (bytesperpixel == 4)
+       {
+               for (i = 0;i < 128;i++)
+               {
+                       for (j = 0;j < 128;j++)
+                       {
+                               solidpixels[(i*128) + j] = ((unsigned *)src)[i*256+j+128];
+                               alphapixels[(i*128) + j] = ((unsigned *)src)[i*256+j];
+                       }
+               }
+       }
+       else
+       {
+               // make an average value for the back to avoid
+               // a fringe on the top level
+               int p, r, g, b;
+               union
+               {
+                       unsigned int i;
+                       unsigned char b[4];
+               }
+               bgra;
+               r = g = b = 0;
+               for (i = 0;i < 128;i++)
+               {
+                       for (j = 0;j < 128;j++)
+                       {
+                               p = src[i*256 + j + 128];
+                               r += palette_rgb[p][0];
+                               g += palette_rgb[p][1];
+                               b += palette_rgb[p][2];
+                       }
+               }
+               bgra.b[2] = r/(128*128);
+               bgra.b[1] = g/(128*128);
+               bgra.b[0] = b/(128*128);
+               bgra.b[3] = 0;
+               for (i = 0;i < 128;i++)
+               {
+                       for (j = 0;j < 128;j++)
+                       {
+                               solidpixels[(i*128) + j] = palette_bgra_complete[src[i*256 + j + 128]];
+                               p = src[i*256 + j];
+                               alphapixels[(i*128) + j] = p ? palette_bgra_complete[p] : bgra.i;
+                       }
+               }
+       }
+
+       loadmodel->brush.solidskytexture = R_LoadTexture2D(loadmodel->texturepool, "sky_solidtexture", 128, 128, (unsigned char *) solidpixels, TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
+       loadmodel->brush.alphaskytexture = R_LoadTexture2D(loadmodel->texturepool, "sky_alphatexture", 128, 128, (unsigned char *) alphapixels, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
+}
+
 static void Mod_Q1BSP_LoadTextures(lump_t *l)
 {
        int i, j, k, num, max, altmax, mtwidth, mtheight, *dofs, incomplete;
+       skinframe_t *skinframe;
        miptex_t *dmiptex;
        texture_t *tx, *tx2, *anims[10], *altanims[10];
        dmiptexlump_t *m;
-       qbyte *data, *mtdata;
-       char name[256];
+       unsigned char *data, *mtdata;
+       const char *s;
+       char mapname[MAX_QPATH], name[MAX_QPATH];
+       unsigned char zero[4];
 
-       loadmodel->brushq1.textures = NULL;
+       memset(zero, 0, sizeof(zero));
+
+       loadmodel->data_textures = NULL;
 
        // add two slots for notexture walls and notexture liquids
        if (l->filelen)
        {
                m = (dmiptexlump_t *)(mod_base + l->fileofs);
                m->nummiptex = LittleLong (m->nummiptex);
-               loadmodel->brushq1.numtextures = m->nummiptex + 2;
+               loadmodel->num_textures = m->nummiptex + 2;
+               loadmodel->num_texturesperskin = loadmodel->num_textures;
        }
        else
        {
                m = NULL;
-               loadmodel->brushq1.numtextures = 2;
+               loadmodel->num_textures = 2;
+               loadmodel->num_texturesperskin = loadmodel->num_textures;
        }
 
-       loadmodel->brushq1.textures = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numtextures * sizeof(texture_t));
+       loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_textures * sizeof(texture_t));
 
        // fill out all slots with notexture
-       for (i = 0, tx = loadmodel->brushq1.textures;i < loadmodel->brushq1.numtextures;i++, tx++)
+       if (cls.state != ca_dedicated)
+               skinframe = R_SkinFrame_LoadMissing();
+       else
+               skinframe = NULL;
+       for (i = 0, tx = loadmodel->data_textures;i < loadmodel->num_textures;i++, tx++)
        {
-               tx->number = i;
-               strcpy(tx->name, "NO TEXTURE FOUND");
+               strlcpy(tx->name, "NO TEXTURE FOUND", sizeof(tx->name));
                tx->width = 16;
                tx->height = 16;
-               tx->skin.base = r_notexture;
-               tx->shader = &Cshader_wall_lightmap;
-               tx->flags = SURF_SOLIDCLIP;
-               if (i == loadmodel->brushq1.numtextures - 1)
+               if (cls.state != ca_dedicated)
                {
-                       tx->flags |= SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
-                       tx->shader = &Cshader_water;
+                       tx->numskinframes = 1;
+                       tx->skinframerate = 1;
+                       tx->skinframes[0] = skinframe;
+                       tx->currentskinframe = tx->skinframes[0];
+               }
+               tx->basematerialflags = MATERIALFLAG_WALL;
+               if (i == loadmodel->num_textures - 1)
+               {
+                       tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW;
+                       tx->supercontents = mod_q1bsp_texture_water.supercontents;
+                       tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags;
+               }
+               else
+               {
+                       tx->supercontents = mod_q1bsp_texture_solid.supercontents;
+                       tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags;
                }
                tx->currentframe = tx;
+
+               // clear water settings
+               tx->reflectmin = 0;
+               tx->reflectmax = 1;
+               tx->refractfactor = 1;
+               Vector4Set(tx->refractcolor4f, 1, 1, 1, 1);
+               tx->reflectfactor = 1;
+               Vector4Set(tx->reflectcolor4f, 1, 1, 1, 1);
+               tx->r_water_wateralpha = 1;
        }
 
        if (!m)
+       {
+               Con_Printf("%s: no miptex lump to load textures from\n", loadmodel->name);
                return;
+       }
+
+       s = loadmodel->name;
+       if (!strncasecmp(s, "maps/", 5))
+               s += 5;
+       FS_StripExtension(s, mapname, sizeof(mapname));
 
        // just to work around bounds checking when debugging with it (array index out of bounds error thing)
        dofs = m->dataofs;
@@ -780,15 +1439,28 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
        for (i = 0;i < m->nummiptex;i++)
        {
                dofs[i] = LittleLong(dofs[i]);
-               if (dofs[i] == -1 || r_nosurftextures.integer)
+               if (r_nosurftextures.integer)
+                       continue;
+               if (dofs[i] == -1)
+               {
+                       Con_DPrintf("%s: miptex #%i missing\n", loadmodel->name, i);
                        continue;
-               dmiptex = (miptex_t *)((qbyte *)m + dofs[i]);
+               }
+               dmiptex = (miptex_t *)((unsigned char *)m + dofs[i]);
 
-               // make sure name is no more than 15 characters
-               for (j = 0;dmiptex->name[j] && j < 15;j++)
+               // copy name, but only up to 16 characters
+               // (the output buffer can hold more than this, but the input buffer is
+               //  only 16)
+               for (j = 0;j < 16 && dmiptex->name[j];j++)
                        name[j] = dmiptex->name[j];
                name[j] = 0;
 
+               if (!name[0])
+               {
+                       dpsnprintf(name, sizeof(name), "unnamed%i", i);
+                       Con_DPrintf("%s: warning: renaming unnamed texture to %s\n", loadmodel->name, name);
+               }
+
                mtwidth = LittleLong(dmiptex->width);
                mtheight = LittleLong(dmiptex->height);
                mtdata = NULL;
@@ -798,139 +1470,148 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                        // texture included
                        if (j < 40 || j + mtwidth * mtheight > l->filelen)
                        {
-                               Con_Printf("Texture \"%s\" in \"%s\"is corrupt or incomplete\n", dmiptex->name, loadmodel->name);
+                               Con_Printf("%s: Texture \"%s\" is corrupt or incomplete\n", loadmodel->name, dmiptex->name);
                                continue;
                        }
-                       mtdata = (qbyte *)dmiptex + j;
+                       mtdata = (unsigned char *)dmiptex + j;
                }
 
                if ((mtwidth & 15) || (mtheight & 15))
-                       Con_Printf("warning: texture \"%s\" in \"%s\" is not 16 aligned", dmiptex->name, loadmodel->name);
+                       Con_DPrintf("%s: warning: texture \"%s\" is not 16 aligned\n", loadmodel->name, dmiptex->name);
 
                // LordHavoc: force all names to lowercase
                for (j = 0;name[j];j++)
                        if (name[j] >= 'A' && name[j] <= 'Z')
                                name[j] += 'a' - 'A';
 
-               tx = loadmodel->brushq1.textures + i;
-               strcpy(tx->name, name);
+               if (dmiptex->name[0] && Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i, name, false, false, 0))
+                       continue;
+
+               tx = loadmodel->data_textures + i;
+               strlcpy(tx->name, name, sizeof(tx->name));
                tx->width = mtwidth;
                tx->height = mtheight;
 
-               if (!tx->name[0])
+               if (tx->name[0] == '*')
+               {
+                       if (!strncmp(tx->name, "*lava", 5))
+                       {
+                               tx->supercontents = mod_q1bsp_texture_lava.supercontents;
+                               tx->surfaceflags = mod_q1bsp_texture_lava.surfaceflags;
+                       }
+                       else if (!strncmp(tx->name, "*slime", 6))
+                       {
+                               tx->supercontents = mod_q1bsp_texture_slime.supercontents;
+                               tx->surfaceflags = mod_q1bsp_texture_slime.surfaceflags;
+                       }
+                       else
+                       {
+                               tx->supercontents = mod_q1bsp_texture_water.supercontents;
+                               tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags;
+                       }
+               }
+               else if (!strncmp(tx->name, "sky", 3))
+               {
+                       tx->supercontents = mod_q1bsp_texture_sky.supercontents;
+                       tx->surfaceflags = mod_q1bsp_texture_sky.surfaceflags;
+               }
+               else
                {
-                       sprintf(tx->name, "unnamed%i", i);
-                       Con_Printf("warning: unnamed texture in %s, renaming to %s\n", loadmodel->name, tx->name);
+                       tx->supercontents = mod_q1bsp_texture_solid.supercontents;
+                       tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags;
                }
 
-               // LordHavoc: HL sky textures are entirely different than quake
-               if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == 256 && mtheight == 128)
+               if (cls.state != ca_dedicated)
                {
-                       if (loadmodel->isworldmodel)
+                       // LordHavoc: HL sky textures are entirely different than quake
+                       if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == 256 && mtheight == 128)
                        {
-                               data = loadimagepixels(tx->name, false, 0, 0);
-                               if (data)
+                               data = loadimagepixelsbgra(tx->name, false, false);
+                               if (data && image_width == 256 && image_height == 128)
                                {
-                                       if (image_width == 256 && image_height == 128)
-                                       {
-                                               R_InitSky(data, 4);
-                                               Mem_Free(data);
-                                       }
-                                       else
-                                       {
-                                               Mem_Free(data);
-                                               Con_Printf("Invalid replacement texture for sky \"%s\" in %\"%s\", must be 256x128 pixels\n", tx->name, loadmodel->name);
-                                               if (mtdata != NULL)
-                                                       R_InitSky(mtdata, 1);
-                                       }
+                                       R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
+                                       Mem_Free(data);
                                }
                                else if (mtdata != NULL)
-                                       R_InitSky(mtdata, 1);
+                                       R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1);
                        }
-               }
-               else
-               {
-                       if (!Mod_LoadSkinFrame(&tx->skin, tx->name, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PRECACHE, false, true, true))
+                       else
                        {
-                               // did not find external texture, load it from the bsp or wad3
-                               if (loadmodel->brush.ishlbsp)
+                               skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s/%s", mapname, tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS, false);
+                               if (!skinframe)
+                                       skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s", tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS, false);
+                               if (!skinframe)
                                {
-                                       // internal texture overrides wad
-                                       qbyte *pixels, *freepixels, *fogpixels;
-                                       pixels = freepixels = NULL;
-                                       if (mtdata)
-                                               pixels = W_ConvertWAD3Texture(dmiptex);
-                                       if (pixels == NULL)
-                                               pixels = freepixels = W_GetTexture(tx->name);
-                                       if (pixels != NULL)
+                                       // did not find external texture, load it from the bsp or wad3
+                                       if (loadmodel->brush.ishlbsp)
                                        {
-                                               tx->width = image_width;
-                                               tx->height = image_height;
-                                               tx->skin.base = tx->skin.merged = R_LoadTexture2D(loadmodel->texturepool, tx->name, image_width, image_height, pixels, TEXTYPE_RGBA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PRECACHE, NULL);
-                                               if (Image_CheckAlpha(pixels, image_width * image_height, true))
+                                               // internal texture overrides wad
+                                               unsigned char *pixels, *freepixels;
+                                               pixels = freepixels = NULL;
+                                               if (mtdata)
+                                                       pixels = W_ConvertWAD3TextureBGRA(dmiptex);
+                                               if (pixels == NULL)
+                                                       pixels = freepixels = W_GetTextureBGRA(tx->name);
+                                               if (pixels != NULL)
                                                {
-                                                       fogpixels = Mem_Alloc(tempmempool, image_width * image_height * 4);
-                                                       for (j = 0;j < image_width * image_height * 4;j += 4)
-                                                       {
-                                                               fogpixels[j + 0] = 255;
-                                                               fogpixels[j + 1] = 255;
-                                                               fogpixels[j + 2] = 255;
-                                                               fogpixels[j + 3] = pixels[j + 3];
-                                                       }
-                                                       tx->skin.fog = R_LoadTexture2D(loadmodel->texturepool, tx->name, image_width, image_height, pixels, TEXTYPE_RGBA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PRECACHE, NULL);
-                                                       Mem_Free(fogpixels);
+                                                       tx->width = image_width;
+                                                       tx->height = image_height;
+                                                       skinframe = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_ALPHA | TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0), pixels, image_width, image_height);
                                                }
+                                               if (freepixels)
+                                                       Mem_Free(freepixels);
                                        }
-                                       if (freepixels)
-                                               Mem_Free(freepixels);
+                                       else if (mtdata) // texture included
+                                               skinframe = R_SkinFrame_LoadInternalQuake(tx->name, TEXF_ALPHA | TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0), false, r_fullbrights.integer, mtdata, tx->width, tx->height);
                                }
-                               else if (mtdata) // texture included
-                                       Mod_LoadSkinFrame_Internal(&tx->skin, tx->name, TEXF_MIPMAP | TEXF_PRECACHE, false, true, tx->name[0] != '*' && r_fullbrights.integer, mtdata, tx->width, tx->height);
+                               // if skinframe is still NULL the "missing" texture will be used
+                               if (skinframe)
+                                       tx->skinframes[0] = skinframe;
                        }
-               }
-               if (tx->skin.base == NULL)
-               {
-                       // no texture found
-                       tx->width = 16;
-                       tx->height = 16;
-                       tx->skin.base = r_notexture;
-               }
 
-               if (tx->name[0] == '*')
-               {
-                       // turb does not block movement
-                       tx->flags &= ~SURF_SOLIDCLIP;
-                       tx->flags |= SURF_DRAWTURB | SURF_LIGHTBOTHSIDES;
-                       // LordHavoc: some turbulent textures should be fullbright and solid
-                       if (!strncmp(tx->name,"*lava",5)
-                        || !strncmp(tx->name,"*teleport",9)
-                        || !strncmp(tx->name,"*rift",5)) // Scourge of Armagon texture
-                               tx->flags |= SURF_DRAWFULLBRIGHT | SURF_DRAWNOALPHA;
-                       else
-                               tx->flags |= SURF_WATERALPHA;
-                       tx->shader = &Cshader_water;
-               }
-               else if (tx->name[0] == 's' && tx->name[1] == 'k' && tx->name[2] == 'y')
-               {
-                       tx->flags |= SURF_DRAWSKY;
-                       tx->shader = &Cshader_sky;
-               }
-               else
-               {
-                       tx->flags |= SURF_LIGHTMAP;
-                       if (!tx->skin.fog)
-                               tx->flags |= SURF_SHADOWCAST | SURF_SHADOWLIGHT;
-                       tx->shader = &Cshader_wall_lightmap;
+                       tx->basematerialflags = MATERIALFLAG_WALL;
+                       if (tx->name[0] == '*')
+                       {
+                               // LordHavoc: some turbulent textures should not be affected by wateralpha
+                               if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae
+                               {
+                                       // replace the texture with transparent black
+                                       Vector4Set(zero, 128, 128, 128, 128);
+                                       tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_MIPMAP | TEXF_PRECACHE | TEXF_ALPHA, zero, 1, 1);
+                                       tx->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_REFLECTION;
+                               }
+                               else if (!strncmp(tx->name,"*lava",5)
+                                || !strncmp(tx->name,"*teleport",9)
+                                || !strncmp(tx->name,"*rift",5)) // Scourge of Armagon texture
+                                       tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW;
+                               else
+                                       tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW | MATERIALFLAG_WATERALPHA | MATERIALFLAG_WATERSHADER;
+                               if (tx->skinframes[0] && tx->skinframes[0]->fog)
+                                       tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW;
+                       }
+                       else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae
+                       {
+                               // replace the texture with black
+                               tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_PRECACHE, zero, 1, 1);
+                               tx->basematerialflags |= MATERIALFLAG_REFLECTION;
+                       }
+                       else if (!strncmp(tx->name, "sky", 3))
+                               tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW;
+                       else if (!strcmp(tx->name, "caulk"))
+                               tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW;
+                       else if (tx->skinframes[0] && tx->skinframes[0]->fog)
+                               tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW;
+
+                       // start out with no animation
+                       tx->currentframe = tx;
+                       tx->currentskinframe = tx->skinframes[0];
                }
-
-               // start out with no animation
-               tx->currentframe = tx;
        }
 
        // sequence the animations
        for (i = 0;i < m->nummiptex;i++)
        {
-               tx = loadmodel->brushq1.textures + i;
+               tx = loadmodel->data_textures + i;
                if (!tx || tx->name[0] != '+' || tx->name[1] == 0 || tx->name[2] == 0)
                        continue;
                if (tx->anim_total[0] || tx->anim_total[1])
@@ -942,7 +1623,7 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
 
                for (j = i;j < m->nummiptex;j++)
                {
-                       tx2 = loadmodel->brushq1.textures + j;
+                       tx2 = loadmodel->data_textures + j;
                        if (!tx2 || tx2->name[0] != '+' || strcmp(tx2->name+2, tx->name+2))
                                continue;
 
@@ -1032,56 +1713,77 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
 static void Mod_Q1BSP_LoadLighting(lump_t *l)
 {
        int i;
-       qbyte *in, *out, *data, d;
-       char litfilename[1024];
-       loadmodel->brushq1.lightdata = NULL;
+       unsigned char *in, *out, *data, d;
+       char litfilename[MAX_QPATH];
+       char dlitfilename[MAX_QPATH];
+       fs_offset_t filesize;
        if (loadmodel->brush.ishlbsp) // LordHavoc: load the colored lighting data straight
        {
-               loadmodel->brushq1.lightdata = Mem_Alloc(loadmodel->mempool, l->filelen);
-               memcpy(loadmodel->brushq1.lightdata, mod_base + l->fileofs, l->filelen);
+               loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen);
+               for (i=0; i<l->filelen; i++)
+                       loadmodel->brushq1.lightdata[i] = mod_base[l->fileofs+i] >>= 1;
        }
        else // LordHavoc: bsp version 29 (normal white lighting)
        {
                // LordHavoc: hope is not lost yet, check for a .lit file to load
                strlcpy (litfilename, loadmodel->name, sizeof (litfilename));
                FS_StripExtension (litfilename, litfilename, sizeof (litfilename));
+               strlcpy (dlitfilename, litfilename, sizeof (dlitfilename));
                strlcat (litfilename, ".lit", sizeof (litfilename));
-               data = (qbyte*) FS_LoadFile(litfilename, false);
+               strlcat (dlitfilename, ".dlit", sizeof (dlitfilename));
+               data = (unsigned char*) FS_LoadFile(litfilename, tempmempool, false, &filesize);
                if (data)
                {
-                       if (fs_filesize > 8 && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T')
+                       if (filesize == (fs_offset_t)(8 + l->filelen * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T')
                        {
                                i = LittleLong(((int *)data)[1]);
                                if (i == 1)
                                {
-                                       Con_DPrintf("loaded %s\n", litfilename);
-                                       loadmodel->brushq1.lightdata = Mem_Alloc(loadmodel->mempool, fs_filesize - 8);
-                                       memcpy(loadmodel->brushq1.lightdata, data + 8, fs_filesize - 8);
+                                       if (developer_loading.integer)
+                                               Con_Printf("loaded %s\n", litfilename);
+                                       loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8);
+                                       memcpy(loadmodel->brushq1.lightdata, data + 8, filesize - 8);
                                        Mem_Free(data);
+                                       data = (unsigned char*) FS_LoadFile(dlitfilename, tempmempool, false, &filesize);
+                                       if (data)
+                                       {
+                                               if (filesize == (fs_offset_t)(8 + l->filelen * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T')
+                                               {
+                                                       i = LittleLong(((int *)data)[1]);
+                                                       if (i == 1)
+                                                       {
+                                                               if (developer_loading.integer)
+                                                                       Con_Printf("loaded %s\n", dlitfilename);
+                                                               loadmodel->brushq1.nmaplightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8);
+                                                               memcpy(loadmodel->brushq1.nmaplightdata, data + 8, filesize - 8);
+                                                               loadmodel->brushq3.deluxemapping_modelspace = false;
+                                                               loadmodel->brushq3.deluxemapping = true;
+                                                       }
+                                               }
+                                               Mem_Free(data);
+                                               data = NULL;
+                                       }
                                        return;
                                }
                                else
-                               {
                                        Con_Printf("Unknown .lit file version (%d)\n", i);
-                                       Mem_Free(data);
-                               }
                        }
+                       else if (filesize == 8)
+                               Con_Print("Empty .lit file, ignoring\n");
                        else
+                               Con_Printf("Corrupt .lit file (file size %i bytes, should be %i bytes), ignoring\n", (int) filesize, (int) (8 + l->filelen * 3));
+                       if (data)
                        {
-                               if (fs_filesize == 8)
-                                       Con_Printf("Empty .lit file, ignoring\n");
-                               else
-                                       Con_Printf("Corrupt .lit file (old version?), ignoring\n");
                                Mem_Free(data);
+                               data = NULL;
                        }
                }
                // LordHavoc: oh well, expand the white lighting data
                if (!l->filelen)
                        return;
-               loadmodel->brushq1.lightdata = Mem_Alloc(loadmodel->mempool, l->filelen*3);
-               in = loadmodel->brushq1.lightdata + l->filelen*2; // place the file at the end, so it will not be overwritten until the very last write
+               loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen*3);
+               in = mod_base + l->fileofs;
                out = loadmodel->brushq1.lightdata;
-               memcpy(in, mod_base + l->fileofs, l->filelen);
                for (i = 0;i < l->filelen;i++)
                {
                        d = *in++;
@@ -1092,66 +1794,6 @@ static void Mod_Q1BSP_LoadLighting(lump_t *l)
        }
 }
 
-static void Mod_Q1BSP_LoadLightList(void)
-{
-       int a, n, numlights;
-       char lightsfilename[1024], *s, *t, *lightsstring;
-       mlight_t *e;
-
-       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)
-       {
-               numlights = 0;
-               while (*s)
-               {
-                       while (*s && *s != '\n')
-                               s++;
-                       if (!*s)
-                       {
-                               Mem_Free(lightsstring);
-                               Host_Error("lights file must end with a newline\n");
-                       }
-                       s++;
-                       numlights++;
-               }
-               loadmodel->brushq1.lights = Mem_Alloc(loadmodel->mempool, numlights * sizeof(mlight_t));
-               s = lightsstring;
-               n = 0;
-               while (*s && n < numlights)
-               {
-                       t = s;
-                       while (*s && *s != '\n')
-                               s++;
-                       if (!*s)
-                       {
-                               Mem_Free(lightsstring);
-                               Host_Error("misparsed lights file!\n");
-                       }
-                       e = loadmodel->brushq1.lights + n;
-                       *s = 0;
-                       a = sscanf(t, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d", &e->origin[0], &e->origin[1], &e->origin[2], &e->falloff, &e->light[0], &e->light[1], &e->light[2], &e->subtract, &e->spotdir[0], &e->spotdir[1], &e->spotdir[2], &e->spotcone, &e->distbias, &e->style);
-                       *s = '\n';
-                       if (a != 14)
-                       {
-                               Mem_Free(lightsstring);
-                               Host_Error("invalid lights file, found %d parameters on line %i, should be 14 parameters (origin[0] origin[1] origin[2] falloff light[0] light[1] light[2] subtract spotdir[0] spotdir[1] spotdir[2] spotcone distancebias style)\n", a, n + 1);
-                       }
-                       s++;
-                       n++;
-               }
-               if (*s)
-               {
-                       Mem_Free(lightsstring);
-                       Host_Error("misparsed lights file!\n");
-               }
-               loadmodel->brushq1.numlights = numlights;
-               Mem_Free(lightsstring);
-       }
-}
-
 static void Mod_Q1BSP_LoadVisibility(lump_t *l)
 {
        loadmodel->brushq1.num_compressedpvs = 0;
@@ -1159,7 +1801,7 @@ static void Mod_Q1BSP_LoadVisibility(lump_t *l)
        if (!l->filelen)
                return;
        loadmodel->brushq1.num_compressedpvs = l->filelen;
-       loadmodel->brushq1.data_compressedpvs = Mem_Alloc(loadmodel->mempool, l->filelen);
+       loadmodel->brushq1.data_compressedpvs = (unsigned char *)Mem_Alloc(loadmodel->mempool, l->filelen);
        memcpy(loadmodel->brushq1.data_compressedpvs, mod_base + l->fileofs, l->filelen);
 }
 
@@ -1167,40 +1809,39 @@ static void Mod_Q1BSP_LoadVisibility(lump_t *l)
 static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data)
 {
        char key[128], value[4096];
-       char wadname[128];
        int i, j, k;
        if (!data)
                return;
-       if (!COM_ParseToken(&data, false))
+       if (!COM_ParseToken_Simple(&data, false, false))
                return; // error
        if (com_token[0] != '{')
                return; // error
        while (1)
        {
-               if (!COM_ParseToken(&data, false))
+               if (!COM_ParseToken_Simple(&data, false, false))
                        return; // error
                if (com_token[0] == '}')
                        break; // end of worldspawn
                if (com_token[0] == '_')
-                       strcpy(key, com_token + 1);
+                       strlcpy(key, com_token + 1, sizeof(key));
                else
-                       strcpy(key, com_token);
+                       strlcpy(key, com_token, sizeof(key));
                while (key[strlen(key)-1] == ' ') // remove trailing spaces
                        key[strlen(key)-1] = 0;
-               if (!COM_ParseToken(&data, false))
+               if (!COM_ParseToken_Simple(&data, false, false))
                        return; // error
-               strcpy(value, com_token);
+               dpsnprintf(value, sizeof(value), "%s", com_token);
                if (!strcmp("wad", key)) // for HalfLife maps
                {
                        if (loadmodel->brush.ishlbsp)
                        {
                                j = 0;
-                               for (i = 0;i < 4096;i++)
+                               for (i = 0;i < (int)sizeof(value);i++)
                                        if (value[i] != ';' && value[i] != '\\' && value[i] != '/' && value[i] != ':')
                                                break;
                                if (value[i])
                                {
-                                       for (;i < 4096;i++)
+                                       for (;i < (int)sizeof(value);i++)
                                        {
                                                // ignore path - the \\ check is for HalfLife... stupid windoze 'programmers'...
                                                if (value[i] == '\\' || value[i] == '/' || value[i] == ':')
@@ -1209,9 +1850,7 @@ static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data)
                                                {
                                                        k = value[i];
                                                        value[i] = 0;
-                                                       strcpy(wadname, "textures/");
-                                                       strcat(wadname, &value[j]);
-                                                       W_LoadTextureWadFile(wadname, false);
+                                                       W_LoadTextureWadFile(&value[j], false);
                                                        j = i+1;
                                                        if (!k)
                                                                break;
@@ -1228,8 +1867,9 @@ static void Mod_Q1BSP_LoadEntities(lump_t *l)
        loadmodel->brush.entities = NULL;
        if (!l->filelen)
                return;
-       loadmodel->brush.entities = Mem_Alloc(loadmodel->mempool, l->filelen);
+       loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1);
        memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen);
+       loadmodel->brush.entities[l->filelen] = 0;
        if (loadmodel->brush.ishlbsp)
                Mod_Q1BSP_ParseWadsFromEntityLump(loadmodel->brush.entities);
 }
@@ -1241,11 +1881,11 @@ static void Mod_Q1BSP_LoadVertexes(lump_t *l)
        mvertex_t       *out;
        int                     i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dvertex_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadVertexes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
+       out = (mvertex_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
 
        loadmodel->brushq1.vertexes = out;
        loadmodel->brushq1.numvertexes = count;
@@ -1258,35 +1898,63 @@ static void Mod_Q1BSP_LoadVertexes(lump_t *l)
        }
 }
 
-static void Mod_Q1BSP_LoadSubmodels(lump_t *l)
+// The following two functions should be removed and MSG_* or SZ_* function sets adjusted so they
+// can be used for this
+// REMOVEME
+int SB_ReadInt (unsigned char **buffer)
+{
+       int     i;
+       i = ((*buffer)[0]) + 256*((*buffer)[1]) + 65536*((*buffer)[2]) + 16777216*((*buffer)[3]);
+       (*buffer) += 4;
+       return i;
+}
+
+// REMOVEME
+float SB_ReadFloat (unsigned char **buffer)
+{
+       union
+       {
+               int             i;
+               float   f;
+       } u;
+
+       u.i = SB_ReadInt (buffer);
+       return u.f;
+}
+
+static void Mod_Q1BSP_LoadSubmodels(lump_t *l, hullinfo_t *hullinfo)
 {
-       dmodel_t        *in;
+       unsigned char           *index;
        dmodel_t        *out;
        int                     i, j, count;
 
-       in = (void *)(mod_base + l->fileofs);
-       if (l->filelen % sizeof(*in))
-               Host_Error("Mod_Q1BSP_LoadSubmodels: funny lump size in %s",loadmodel->name);
-       count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
+       index = (unsigned char *)(mod_base + l->fileofs);
+       if (l->filelen % (48+4*hullinfo->filehulls))
+               Host_Error ("Mod_Q1BSP_LoadSubmodels: funny lump size in %s", loadmodel->name);
+
+       count = l->filelen / (48+4*hullinfo->filehulls);
+       out = (dmodel_t *)Mem_Alloc (loadmodel->mempool, count*sizeof(*out));
 
        loadmodel->brushq1.submodels = out;
        loadmodel->brush.numsubmodels = count;
 
-       for ( i=0 ; i<count ; i++, in++, out++)
+       for (i = 0; i < count; i++, out++)
        {
-               for (j=0 ; j<3 ; j++)
-               {
-                       // spread the mins / maxs by a pixel
-                       out->mins[j] = LittleFloat(in->mins[j]) - 1;
-                       out->maxs[j] = LittleFloat(in->maxs[j]) + 1;
-                       out->origin[j] = LittleFloat(in->origin[j]);
-               }
-               for (j=0 ; j<MAX_MAP_HULLS ; j++)
-                       out->headnode[j] = LittleLong(in->headnode[j]);
-               out->visleafs = LittleLong(in->visleafs);
-               out->firstface = LittleLong(in->firstface);
-               out->numfaces = LittleLong(in->numfaces);
+       // spread out the mins / maxs by a pixel
+               out->mins[0] = SB_ReadFloat (&index) - 1;
+               out->mins[1] = SB_ReadFloat (&index) - 1;
+               out->mins[2] = SB_ReadFloat (&index) - 1;
+               out->maxs[0] = SB_ReadFloat (&index) + 1;
+               out->maxs[1] = SB_ReadFloat (&index) + 1;
+               out->maxs[2] = SB_ReadFloat (&index) + 1;
+               out->origin[0] = SB_ReadFloat (&index);
+               out->origin[1] = SB_ReadFloat (&index);
+               out->origin[2] = SB_ReadFloat (&index);
+               for (j = 0; j < hullinfo->filehulls; j++)
+                       out->headnode[j] = SB_ReadInt (&index);
+               out->visleafs = SB_ReadInt (&index);
+               out->firstface = SB_ReadInt (&index);
+               out->numfaces = SB_ReadInt (&index);
        }
 }
 
@@ -1296,11 +1964,11 @@ static void Mod_Q1BSP_LoadEdges(lump_t *l)
        medge_t *out;
        int     i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dedge_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadEdges: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (medge_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
        loadmodel->brushq1.edges = out;
        loadmodel->brushq1.numedges = count;
@@ -1309,6 +1977,12 @@ static void Mod_Q1BSP_LoadEdges(lump_t *l)
        {
                out->v[0] = (unsigned short)LittleShort(in->v[0]);
                out->v[1] = (unsigned short)LittleShort(in->v[1]);
+               if (out->v[0] >= loadmodel->brushq1.numvertexes || out->v[1] >= loadmodel->brushq1.numvertexes)
+               {
+                       Con_Printf("Mod_Q1BSP_LoadEdges: %s has invalid vertex indices in edge %i (vertices %i %i >= numvertices %i)\n", loadmodel->name, i, out->v[0], out->v[1], loadmodel->brushq1.numvertexes);
+                       out->v[0] = 0;
+                       out->v[1] = 0;
+               }
        }
 }
 
@@ -1318,11 +1992,11 @@ static void Mod_Q1BSP_LoadTexinfo(lump_t *l)
        mtexinfo_t *out;
        int i, j, k, count, miptex;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (texinfo_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadTexinfo: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (mtexinfo_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
        loadmodel->brushq1.texinfo = out;
        loadmodel->brushq1.numtexinfo = count;
@@ -1337,25 +2011,25 @@ static void Mod_Q1BSP_LoadTexinfo(lump_t *l)
                out->flags = LittleLong(in->flags);
 
                out->texture = NULL;
-               if (loadmodel->brushq1.textures)
+               if (loadmodel->data_textures)
                {
-                       if ((unsigned int) miptex >= (unsigned int) loadmodel->brushq1.numtextures)
-                               Con_Printf("error in model \"%s\": invalid miptex index %i(of %i)\n", loadmodel->name, miptex, loadmodel->brushq1.numtextures);
+                       if ((unsigned int) miptex >= (unsigned int) loadmodel->num_textures)
+                               Con_Printf("error in model \"%s\": invalid miptex index %i(of %i)\n", loadmodel->name, miptex, loadmodel->num_textures);
                        else
-                               out->texture = loadmodel->brushq1.textures + miptex;
+                               out->texture = loadmodel->data_textures + miptex;
                }
                if (out->flags & TEX_SPECIAL)
                {
                        // if texture chosen is NULL or the shader needs a lightmap,
                        // force to notexture water shader
-                       if (out->texture == NULL || out->texture->shader->flags & SHADERFLAGS_NEEDLIGHTMAP)
-                               out->texture = loadmodel->brushq1.textures + (loadmodel->brushq1.numtextures - 1);
+                       if (out->texture == NULL)
+                               out->texture = loadmodel->data_textures + (loadmodel->num_textures - 1);
                }
                else
                {
                        // if texture chosen is NULL, force to notexture
                        if (out->texture == NULL)
-                               out->texture = loadmodel->brushq1.textures + (loadmodel->brushq1.numtextures - 2);
+                               out->texture = loadmodel->data_textures + (loadmodel->num_textures - 2);
                }
        }
 }
@@ -1464,7 +2138,7 @@ static void SubdividePolygon(int numverts, float *verts)
        {
                if (subdivpolytriangles >= MAX_SUBDIVPOLYTRIANGLES)
                {
-                       Con_Printf("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n");
+                       Con_Print("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n");
                        return;
                }
 
@@ -1480,7 +2154,7 @@ static void SubdividePolygon(int numverts, float *verts)
 //Breaks a polygon up along axial 64 unit
 //boundaries so that turbulent and sky warps
 //can be done reasonably.
-static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surf)
+static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surface)
 {
        int i, j;
        surfvertex_t *v;
@@ -1488,11 +2162,11 @@ static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surf)
 
        subdivpolytriangles = 0;
        subdivpolyverts = 0;
-       SubdividePolygon(surf->poly_numverts, surf->poly_verts);
+       SubdividePolygon(surface->num_vertices, (surface->mesh->data_vertex3f + 3 * surface->num_firstvertex));
        if (subdivpolytriangles < 1)
-               Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?\n");
+               Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?");
 
-       surf->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t));
+       surface->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t));
        mesh->num_vertices = subdivpolyverts;
        mesh->num_triangles = subdivpolytriangles;
        mesh->vertex = (surfvertex_t *)(mesh + 1);
@@ -1506,257 +2180,376 @@ static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surf)
        for (i = 0, v = mesh->vertex;i < subdivpolyverts;i++, v++)
        {
                VectorCopy(subdivpolyvert[i], v->v);
-               v->st[0] = DotProduct(v->v, surf->texinfo->vecs[0]);
-               v->st[1] = DotProduct(v->v, surf->texinfo->vecs[1]);
+               v->st[0] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[0]);
+               v->st[1] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[1]);
        }
 }
 #endif
 
-static surfmesh_t *Mod_Q1BSP_AllocSurfMesh(int numverts, int numtriangles)
+/* Maximum size of a single LM */
+#define MAX_SINGLE_LM_SIZE    256
+
+struct alloc_lm_row
 {
-       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->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;
+       int rowY;
+       int currentX;
+};
+
+struct alloc_lm_state
+{
+       int currentY;
+       struct alloc_lm_row rows[MAX_SINGLE_LM_SIZE];
+};
+
+static void init_alloc_lm_state (struct alloc_lm_state* state)
+{
+       int r;
+
+       state->currentY = 0;
+       for (r = 0; r < MAX_SINGLE_LM_SIZE; r++)
+       {
+         state->rows[r].currentX = 0;
+         state->rows[r].rowY = -1;
+       }
 }
 
-static void Mod_Q1BSP_GenerateSurfacePolygon(msurface_t *surf, int firstedge, int numedges)
+static qboolean Mod_Q1BSP_AllocLightmapBlock(struct alloc_lm_state* state, int totalwidth, int totalheight, int blockwidth, int blockheight, int *outx, int *outy)
 {
-       int i, lindex, j;
-       float *vec, *vert, mins[3], maxs[3], val, *v;
-       mtexinfo_t *tex;
-
-       // convert edges back to a normal polygon
-       surf->poly_numverts = numedges;
-       vert = surf->poly_verts = Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * numedges);
-       for (i = 0;i < numedges;i++)
-       {
-               lindex = loadmodel->brushq1.surfedges[firstedge + i];
-               if (lindex > 0)
-                       vec = loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[lindex].v[0]].position;
-               else
-                       vec = loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[-lindex].v[1]].position;
-               VectorCopy(vec, vert);
-               vert += 3;
-       }
-
-       // calculate polygon bounding box and center
-       vert = surf->poly_verts;
-       VectorCopy(vert, mins);
-       VectorCopy(vert, maxs);
-       vert += 3;
-       for (i = 1;i < surf->poly_numverts;i++, vert += 3)
-       {
-               if (mins[0] > vert[0]) mins[0] = vert[0];if (maxs[0] < vert[0]) maxs[0] = vert[0];
-               if (mins[1] > vert[1]) mins[1] = vert[1];if (maxs[1] < vert[1]) maxs[1] = vert[1];
-               if (mins[2] > vert[2]) mins[2] = vert[2];if (maxs[2] < vert[2]) maxs[2] = vert[2];
-       }
-       VectorCopy(mins, surf->poly_mins);
-       VectorCopy(maxs, surf->poly_maxs);
-       surf->poly_center[0] = (mins[0] + maxs[0]) * 0.5f;
-       surf->poly_center[1] = (mins[1] + maxs[1]) * 0.5f;
-       surf->poly_center[2] = (mins[2] + maxs[2]) * 0.5f;
-
-       // generate surface extents information
-       tex = surf->texinfo;
-       mins[0] = maxs[0] = DotProduct(surf->poly_verts, tex->vecs[0]) + tex->vecs[0][3];
-       mins[1] = maxs[1] = DotProduct(surf->poly_verts, tex->vecs[1]) + tex->vecs[1][3];
-       for (i = 1, v = surf->poly_verts + 3;i < surf->poly_numverts;i++, v += 3)
+       struct alloc_lm_row* row;
+       int r;
+
+       row = &(state->rows[blockheight]);
+       if ((row->rowY < 0) || (row->currentX + blockwidth > totalwidth))
        {
-               for (j = 0;j < 2;j++)
+               if (state->currentY + blockheight <= totalheight)
                {
-                       val = DotProduct(v, tex->vecs[j]) + tex->vecs[j][3];
-                       if (mins[j] > val)
-                               mins[j] = val;
-                       if (maxs[j] < val)
-                               maxs[j] = val;
+                       row->rowY = state->currentY;
+                       row->currentX = 0;
+                       state->currentY += blockheight;
+               }
+               else
+               {
+                       /* See if we can stuff the block into a higher row */
+                       row = NULL;
+                       for (r = blockheight; r < MAX_SINGLE_LM_SIZE; r++)
+                       {
+                               if ((state->rows[r].rowY >= 0)
+                                 && (state->rows[r].currentX + blockwidth <= totalwidth))
+                               {
+                                       row = &(state->rows[r]);
+                                       break;
+                               }
+                       }
+                       if (row == NULL) return false;
                }
        }
-       for (i = 0;i < 2;i++)
-       {
-               surf->texturemins[i] = (int) floor(mins[i] / 16) * 16;
-               surf->extents[i] = (int) ceil(maxs[i] / 16) * 16 - surf->texturemins[i];
-       }
+       *outy = row->rowY;
+       *outx = row->currentX;
+       row->currentX += blockwidth;
+
+       return true;
 }
 
+extern cvar_t gl_max_size;
 static void Mod_Q1BSP_LoadFaces(lump_t *l)
 {
        dface_t *in;
-       msurface_t *surf;
-       int i, count, surfnum, planenum, ssize, tsize, firstedge, numedges, totalverts, totaltris, totalmeshes;
-       surfmesh_t *mesh;
-       float s, t;
+       msurface_t *surface;
+       int i, j, count, surfacenum, planenum, smax, tmax, ssize, tsize, firstedge, numedges, totalverts, totaltris, lightmapnumber, lightmapsize, totallightmapsamples;
+       float texmins[2], texmaxs[2], val;
+       rtexture_t *lightmaptexture, *deluxemaptexture;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dface_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadFaces: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       loadmodel->brushq1.surfaces = Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_t));
+       loadmodel->data_surfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_t));
+       loadmodel->data_surfaces_lightmapinfo = (msurface_lightmapinfo_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_lightmapinfo_t));
+
+       loadmodel->num_surfaces = count;
 
-       loadmodel->brushq1.numsurfaces = count;
-       loadmodel->brushq1.surfacevisframes = Mem_Alloc(loadmodel->mempool, count * sizeof(int));
-       loadmodel->brushq1.surfacepvsframes = Mem_Alloc(loadmodel->mempool, count * sizeof(int));
-       loadmodel->brushq1.pvssurflist = Mem_Alloc(loadmodel->mempool, count * sizeof(int));
+       loadmodel->brushq1.lightmapupdateflags = (unsigned char *)Mem_Alloc(loadmodel->mempool, count*sizeof(unsigned char));
 
-       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++, in++, surf++)
+       totalverts = 0;
+       totaltris = 0;
+       for (surfacenum = 0, in = (dface_t *)(mod_base + l->fileofs);surfacenum < count;surfacenum++, in++)
+       {
+               numedges = (unsigned short)LittleShort(in->numedges);
+               totalverts += numedges;
+               totaltris += numedges - 2;
+       }
+
+       Mod_AllocSurfMesh(loadmodel->mempool, totalverts, totaltris, true, false, false);
+
+       lightmaptexture = NULL;
+       deluxemaptexture = r_texture_blanknormalmap;
+       lightmapnumber = 1;
+       lightmapsize = max(256, gl_max_size.integer);
+       totallightmapsamples = 0;
+
+       totalverts = 0;
+       totaltris = 0;
+       for (surfacenum = 0, in = (dface_t *)(mod_base + l->fileofs), surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, in++, surface++)
        {
-               surf->number = surfnum;
+               surface->lightmapinfo = loadmodel->data_surfaces_lightmapinfo + surfacenum;
+
                // FIXME: validate edges, texinfo, etc?
                firstedge = LittleLong(in->firstedge);
-               numedges = LittleShort(in->numedges);
+               numedges = (unsigned short)LittleShort(in->numedges);
                if ((unsigned int) firstedge > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) firstedge + (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges)
-                       Host_Error("Mod_Q1BSP_LoadFaces: invalid edge range (firstedge %i, numedges %i, model edges %i)\n", firstedge, numedges, loadmodel->brushq1.numsurfedges);
-               i = LittleShort(in->texinfo);
+                       Host_Error("Mod_Q1BSP_LoadFaces: invalid edge range (firstedge %i, numedges %i, model edges %i)", firstedge, numedges, loadmodel->brushq1.numsurfedges);
+               i = (unsigned short)LittleShort(in->texinfo);
                if ((unsigned int) i >= (unsigned int) loadmodel->brushq1.numtexinfo)
-                       Host_Error("Mod_Q1BSP_LoadFaces: invalid texinfo index %i(model has %i texinfos)\n", i, loadmodel->brushq1.numtexinfo);
-               surf->texinfo = loadmodel->brushq1.texinfo + i;
-               surf->flags = surf->texinfo->texture->flags;
-
-               planenum = LittleShort(in->planenum);
-               if ((unsigned int) planenum >= (unsigned int) loadmodel->brushq1.numplanes)
-                       Host_Error("Mod_Q1BSP_LoadFaces: invalid plane index %i (model has %i planes)\n", planenum, loadmodel->brushq1.numplanes);
-
-               if (LittleShort(in->side))
-                       surf->flags |= SURF_PLANEBACK;
-
-               surf->plane = loadmodel->brushq1.planes + planenum;
+                       Host_Error("Mod_Q1BSP_LoadFaces: invalid texinfo index %i(model has %i texinfos)", i, loadmodel->brushq1.numtexinfo);
+               surface->lightmapinfo->texinfo = loadmodel->brushq1.texinfo + i;
+               surface->texture = surface->lightmapinfo->texinfo->texture;
+
+               planenum = (unsigned short)LittleShort(in->planenum);
+               if ((unsigned int) planenum >= (unsigned int) loadmodel->brush.num_planes)
+                       Host_Error("Mod_Q1BSP_LoadFaces: invalid plane index %i (model has %i planes)", planenum, loadmodel->brush.num_planes);
+
+               //surface->flags = surface->texture->flags;
+               //if (LittleShort(in->side))
+               //      surface->flags |= SURF_PLANEBACK;
+               //surface->plane = loadmodel->brush.data_planes + planenum;
+
+               surface->num_firstvertex = totalverts;
+               surface->num_vertices = numedges;
+               surface->num_firsttriangle = totaltris;
+               surface->num_triangles = numedges - 2;
+               totalverts += numedges;
+               totaltris += numedges - 2;
+
+               // convert edges back to a normal polygon
+               for (i = 0;i < surface->num_vertices;i++)
+               {
+                       int lindex = loadmodel->brushq1.surfedges[firstedge + i];
+                       float s, t;
+                       if (lindex > 0)
+                               VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[lindex].v[0]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3);
+                       else
+                               VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[-lindex].v[1]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3);
+                       s = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3];
+                       t = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3];
+                       (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 0] = s / surface->texture->width;
+                       (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 1] = t / surface->texture->height;
+                       (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = 0;
+                       (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = 0;
+                       (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = 0;
+               }
 
-               // clear lightmap (filled in later)
-               surf->lightmaptexture = NULL;
+               for (i = 0;i < surface->num_triangles;i++)
+               {
+                       (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 0] = 0 + surface->num_firstvertex;
+                       (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 1] = i + 1 + surface->num_firstvertex;
+                       (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 2] = i + 2 + surface->num_firstvertex;
+               }
 
-               // force lightmap upload on first time seeing the surface
-               surf->cached_dlight = true;
+               // compile additional data about the surface geometry
+               Mod_BuildNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_normal3f, true);
+               Mod_BuildTextureVectorsFromNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, true);
+               BoxFromPoints(surface->mins, surface->maxs, surface->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex));
 
-               Mod_Q1BSP_GenerateSurfacePolygon(surf, firstedge, numedges);
+               // generate surface extents information
+               texmins[0] = texmaxs[0] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3];
+               texmins[1] = texmaxs[1] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3];
+               for (i = 1;i < surface->num_vertices;i++)
+               {
+                       for (j = 0;j < 2;j++)
+                       {
+                               val = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3, surface->lightmapinfo->texinfo->vecs[j]) + surface->lightmapinfo->texinfo->vecs[j][3];
+                               texmins[j] = min(texmins[j], val);
+                               texmaxs[j] = max(texmaxs[j], val);
+                       }
+               }
+               for (i = 0;i < 2;i++)
+               {
+                       surface->lightmapinfo->texturemins[i] = (int) floor(texmins[i] / 16.0) * 16;
+                       surface->lightmapinfo->extents[i] = (int) ceil(texmaxs[i] / 16.0) * 16 - surface->lightmapinfo->texturemins[i];
+               }
 
-               ssize = (surf->extents[0] >> 4) + 1;
-               tsize = (surf->extents[1] >> 4) + 1;
+               smax = surface->lightmapinfo->extents[0] >> 4;
+               tmax = surface->lightmapinfo->extents[1] >> 4;
+               ssize = (surface->lightmapinfo->extents[0] >> 4) + 1;
+               tsize = (surface->lightmapinfo->extents[1] >> 4) + 1;
 
                // lighting info
                for (i = 0;i < MAXLIGHTMAPS;i++)
-                       surf->styles[i] = in->styles[i];
+                       surface->lightmapinfo->styles[i] = in->styles[i];
+               surface->lightmaptexture = NULL;
+               surface->deluxemaptexture = r_texture_blanknormalmap;
                i = LittleLong(in->lightofs);
                if (i == -1)
-                       surf->samples = NULL;
+               {
+                       surface->lightmapinfo->samples = NULL;
+#if 0
+                       // give non-lightmapped water a 1x white lightmap
+                       if (surface->texture->name[0] == '*' && (surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) && ssize <= 256 && tsize <= 256)
+                       {
+                               surface->lightmapinfo->samples = (unsigned char *)Mem_Alloc(loadmodel->mempool, ssize * tsize * 3);
+                               surface->lightmapinfo->styles[0] = 0;
+                               memset(surface->lightmapinfo->samples, 128, ssize * tsize * 3);
+                       }
+#endif
+               }
                else if (loadmodel->brush.ishlbsp) // LordHavoc: HalfLife map (bsp version 30)
-                       surf->samples = loadmodel->brushq1.lightdata + i;
+                       surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + i;
                else // LordHavoc: white lighting (bsp version 29)
-                       surf->samples = loadmodel->brushq1.lightdata + (i * 3);
-
-               if (surf->texinfo->texture->shader == &Cshader_wall_lightmap)
                {
-                       if ((surf->extents[0] >> 4) + 1 > (256) || (surf->extents[1] >> 4) + 1 > (256))
-                               Host_Error("Bad surface extents");
-                       // stainmap for permanent marks on walls
-                       surf->stainsamples = Mem_Alloc(loadmodel->mempool, ssize * tsize * 3);
-                       // clear to white
-                       memset(surf->stainsamples, 255, ssize * tsize * 3);
+                       surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + (i * 3);
+                       if (loadmodel->brushq1.nmaplightdata)
+                               surface->lightmapinfo->nmapsamples = loadmodel->brushq1.nmaplightdata + (i * 3);
                }
-       }
 
-       loadmodel->brushq1.entiremesh = Mod_Q1BSP_AllocSurfMesh(totalverts, totaltris);
-
-       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;
-               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;
+               // check if we should apply a lightmap to this
+               if (!(surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) || surface->lightmapinfo->samples)
+               {
+                       if (ssize > 256 || tsize > 256)
+                               Host_Error("Bad surface extents");
 
-               surf->lightmaptexturestride = 0;
-               surf->lightmaptexture = NULL;
+                       if (lightmapsize < ssize)
+                               lightmapsize = ssize;
+                       if (lightmapsize < tsize)
+                               lightmapsize = tsize;
 
-               for (i = 0;i < mesh->num_vertices;i++)
-               {
-                       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;
-               }
+                       totallightmapsamples += ssize*tsize;
 
-               for (i = 0;i < mesh->num_triangles;i++)
-               {
-                       mesh->data_element3i[i * 3 + 0] = 0;
-                       mesh->data_element3i[i * 3 + 1] = i + 1;
-                       mesh->data_element3i[i * 3 + 2] = i + 2;
+                       // force lightmap upload on first time seeing the surface
+                       //
+                       // additionally this is used by the later code to see if a
+                       // lightmap is needed on this surface (rather than duplicating the
+                       // logic above)
+                       loadmodel->brushq1.lightmapupdateflags[surfacenum] = true;
                }
+       }
 
-               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);
+       // small maps (such as ammo boxes especially) don't need big lightmap
+       // textures, so this code tries to guess a good size based on
+       // totallightmapsamples (size of the lightmaps lump basically), as well as
+       // trying to max out the gl_max_size if there is a lot of lightmap data to
+       // store
+       // additionally, never choose a lightmapsize that is smaller than the
+       // largest surface encountered (as it would fail)
+       i = lightmapsize;
+       for (lightmapsize = 64; (lightmapsize < i) && (lightmapsize < gl_max_size.integer) && (totallightmapsamples > lightmapsize*lightmapsize); lightmapsize*=2)
+               ;
+
+       // now that we've decided the lightmap texture size, we can do the rest
+       if (cls.state != ca_dedicated)
+       {
+               int stainmapsize = 0;
+               struct alloc_lm_state allocState;
 
-               if (surf->texinfo->texture->shader == &Cshader_wall_lightmap)
+               for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++)
                {
-                       int i, iu, iv, smax, tmax;
+                       int i, iu, iv, lightmapx = 0, lightmapy = 0;
                        float u, v, ubase, vbase, uscale, vscale;
 
-                       smax = surf->extents[0] >> 4;
-                       tmax = surf->extents[1] >> 4;
+                       if (!loadmodel->brushq1.lightmapupdateflags[surfacenum])
+                               continue;
 
-                       surf->flags |= SURF_LIGHTMAP;
-                       if (r_miplightmaps.integer)
-                       {
-                               surf->lightmaptexturestride = smax+1;
-                               surf->lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, NULL, surf->lightmaptexturestride, (surf->extents[1]>>4)+1, NULL, loadmodel->brushq1.lightmaprgba ? TEXTYPE_RGBA : TEXTYPE_RGB, TEXF_MIPMAP | TEXF_FORCELINEAR | TEXF_PRECACHE, NULL);
-                       }
-                       else
+                       smax = surface->lightmapinfo->extents[0] >> 4;
+                       tmax = surface->lightmapinfo->extents[1] >> 4;
+                       ssize = (surface->lightmapinfo->extents[0] >> 4) + 1;
+                       tsize = (surface->lightmapinfo->extents[1] >> 4) + 1;
+                       stainmapsize += ssize * tsize * 3;
+
+                       if (!lightmaptexture || !Mod_Q1BSP_AllocLightmapBlock(&allocState, lightmapsize, lightmapsize, ssize, tsize, &lightmapx, &lightmapy))
                        {
-                               surf->lightmaptexturestride = R_CompatibleFragmentWidth(smax+1, loadmodel->brushq1.lightmaprgba ? TEXTYPE_RGBA : TEXTYPE_RGB, 0);
-                               surf->lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, NULL, surf->lightmaptexturestride, (surf->extents[1]>>4)+1, NULL, loadmodel->brushq1.lightmaprgba ? TEXTYPE_RGBA : TEXTYPE_RGB, TEXF_FRAGMENT | TEXF_FORCELINEAR | TEXF_PRECACHE, NULL);
+                               // allocate a texture pool if we need it
+                               if (loadmodel->texturepool == NULL)
+                                       loadmodel->texturepool = R_AllocTexturePool();
+                               // could not find room, make a new lightmap
+                               lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE, NULL);
+                               if (loadmodel->brushq1.nmaplightdata)
+                                       deluxemaptexture = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE, NULL);
+                               lightmapnumber++;
+                               init_alloc_lm_state (&allocState);
+                               Mod_Q1BSP_AllocLightmapBlock(&allocState, lightmapsize, lightmapsize, ssize, tsize, &lightmapx, &lightmapy);
                        }
-                       R_FragmentLocation(surf->lightmaptexture, NULL, NULL, &ubase, &vbase, &uscale, &vscale);
-                       uscale = (uscale - ubase) / (smax + 1);
-                       vscale = (vscale - vbase) / (tmax + 1);
+                       surface->lightmaptexture = lightmaptexture;
+                       surface->deluxemaptexture = deluxemaptexture;
+                       surface->lightmapinfo->lightmaporigin[0] = lightmapx;
+                       surface->lightmapinfo->lightmaporigin[1] = lightmapy;
 
-                       for (i = 0;i < mesh->num_vertices;i++)
+                       uscale = 1.0f / (float)lightmapsize;
+                       vscale = 1.0f / (float)lightmapsize;
+                       ubase = lightmapx * uscale;
+                       vbase = lightmapy * vscale;
+
+                       for (i = 0;i < surface->num_vertices;i++)
                        {
-                               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;
+                               u = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]) + 8 - surface->lightmapinfo->texturemins[0]) * (1.0 / 16.0);
+                               v = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]) + 8 - surface->lightmapinfo->texturemins[1]) * (1.0 / 16.0);
+                               (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = u * uscale + ubase;
+                               (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = v * vscale + vbase;
                                // LordHavoc: calc lightmap data offset for vertex lighting to use
                                iu = (int) u;
                                iv = (int) v;
-                               mesh->data_lightmapoffsets[i] = (bound(0, iv, tmax) * (smax+1) + bound(0, iu, smax)) * 3;
+                               (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = (bound(0, iv, tmax) * ssize + bound(0, iu, smax)) * 3;
+                       }
+               }
+
+               if (cl_stainmaps.integer)
+               {
+                       // allocate stainmaps for permanent marks on walls and clear white
+                       unsigned char *stainsamples = NULL;
+                       stainsamples = (unsigned char *)Mem_Alloc(loadmodel->mempool, stainmapsize);
+                       memset(stainsamples, 255, stainmapsize);
+                       // assign pointers
+                       for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++)
+                       {
+                               if (!loadmodel->brushq1.lightmapupdateflags[surfacenum])
+                                       continue;
+                               ssize = (surface->lightmapinfo->extents[0] >> 4) + 1;
+                               tsize = (surface->lightmapinfo->extents[1] >> 4) + 1;
+                               surface->lightmapinfo->stainsamples = stainsamples;
+                               stainsamples += ssize * tsize * 3;
                        }
                }
        }
+
+       // generate ushort elements array if possible
+       if (loadmodel->surfmesh.data_element3s)
+               for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++)
+                       loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i];
 }
 
-static void Mod_Q1BSP_SetParent(mnode_t *node, mnode_t *parent)
+static void Mod_Q1BSP_LoadNodes_RecursiveSetParent(mnode_t *node, mnode_t *parent)
 {
+       //if (node->parent)
+       //      Host_Error("Mod_Q1BSP_LoadNodes_RecursiveSetParent: runaway recursion");
        node->parent = parent;
-       if (node->contents < 0)
-               return;
-       Mod_Q1BSP_SetParent(node->children[0], node);
-       Mod_Q1BSP_SetParent(node->children[1], node);
+       if (node->plane)
+       {
+               // this is a node, recurse to children
+               Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[0], node);
+               Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[1], node);
+               // combine supercontents of children
+               node->combinedsupercontents = node->children[0]->combinedsupercontents | node->children[1]->combinedsupercontents;
+       }
+       else
+       {
+               int j;
+               mleaf_t *leaf = (mleaf_t *)node;
+               // if this is a leaf, calculate supercontents mask from all collidable
+               // primitives in the leaf (brushes and collision surfaces)
+               // also flag if the leaf contains any collision surfaces
+               leaf->combinedsupercontents = 0;
+               // combine the supercontents values of all brushes in this leaf
+               for (j = 0;j < leaf->numleafbrushes;j++)
+                       leaf->combinedsupercontents |= loadmodel->brush.data_brushes[leaf->firstleafbrush[j]].texture->supercontents;
+               // check if this leaf contains any collision surfaces (q3 patches)
+               for (j = 0;j < leaf->numleafsurfaces;j++)
+               {
+                       msurface_t *surface = loadmodel->data_surfaces + leaf->firstleafsurface[j];
+                       if (surface->num_collisiontriangles)
+                       {
+                               leaf->containscollisionsurfaces = true;
+                               leaf->combinedsupercontents |= surface->texture->supercontents;
+                       }
+               }
+       }
 }
 
 static void Mod_Q1BSP_LoadNodes(lump_t *l)
@@ -1765,14 +2558,14 @@ static void Mod_Q1BSP_LoadNodes(lump_t *l)
        dnode_t         *in;
        mnode_t         *out;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dnode_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadNodes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
+       out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
 
-       loadmodel->brushq1.nodes = out;
-       loadmodel->brushq1.numnodes = count;
+       loadmodel->brush.data_nodes = out;
+       loadmodel->brush.num_nodes = count;
 
        for ( i=0 ; i<count ; i++, in++, out++)
        {
@@ -1783,41 +2576,67 @@ static void Mod_Q1BSP_LoadNodes(lump_t *l)
                }
 
                p = LittleLong(in->planenum);
-               out->plane = loadmodel->brushq1.planes + p;
+               out->plane = loadmodel->brush.data_planes + p;
 
-               out->firstsurface = LittleShort(in->firstface);
-               out->numsurfaces = LittleShort(in->numfaces);
+               out->firstsurface = (unsigned short)LittleShort(in->firstface);
+               out->numsurfaces = (unsigned short)LittleShort(in->numfaces);
 
                for (j=0 ; j<2 ; j++)
                {
-                       p = LittleShort(in->children[j]);
-                       if (p >= 0)
-                               out->children[j] = loadmodel->brushq1.nodes + p;
+                       // LordHavoc: this code supports broken bsp files produced by
+                       // arguire qbsp which can produce more than 32768 nodes, any value
+                       // below count is assumed to be a node number, any other value is
+                       // assumed to be a leaf number
+                       p = (unsigned short)LittleShort(in->children[j]);
+                       if (p < count)
+                       {
+                               if (p < loadmodel->brush.num_nodes)
+                                       out->children[j] = loadmodel->brush.data_nodes + p;
+                               else
+                               {
+                                       Con_Printf("Mod_Q1BSP_LoadNodes: invalid node index %i (file has only %i nodes)\n", p, loadmodel->brush.num_nodes);
+                                       // map it to the solid leaf
+                                       out->children[j] = (mnode_t *)loadmodel->brush.data_leafs;
+                               }
+                       }
                        else
-                               out->children[j] = (mnode_t *)(loadmodel->brushq1.leafs + (-1 - p));
+                       {
+                               // note this uses 65535 intentionally, -1 is leaf 0
+                               p = 65535 - p;
+                               if (p < loadmodel->brush.num_leafs)
+                                       out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + p);
+                               else
+                               {
+                                       Con_Printf("Mod_Q1BSP_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->brush.num_leafs);
+                                       // map it to the solid leaf
+                                       out->children[j] = (mnode_t *)loadmodel->brush.data_leafs;
+                               }
+                       }
                }
        }
 
-       Mod_Q1BSP_SetParent(loadmodel->brushq1.nodes, NULL);    // sets nodes and leafs
+       Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL);      // sets nodes and leafs
 }
 
 static void Mod_Q1BSP_LoadLeafs(lump_t *l)
 {
        dleaf_t *in;
        mleaf_t *out;
-       int i, j, count, p, pvschainbytes;
-       qbyte *pvs;
+       int i, j, count, p;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dleaf_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadLeafs: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
+       out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
 
-       loadmodel->brushq1.leafs = out;
-       loadmodel->brushq1.numleafs = count;
-       pvschainbytes = ((loadmodel->brushq1.numleafs - 1)+7)>>3;
-       loadmodel->brushq1.data_decompressedpvs = pvs = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numleafs * pvschainbytes);
+       loadmodel->brush.data_leafs = out;
+       loadmodel->brush.num_leafs = count;
+       // get visleafs from the submodel data
+       loadmodel->brush.num_pvsclusters = loadmodel->brushq1.submodels[0].visleafs;
+       loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters+7)>>3;
+       loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes);
+       memset(loadmodel->brush.data_pvsclusters, 0xFF, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes);
 
        for ( i=0 ; i<count ; i++, in++, out++)
        {
@@ -1831,26 +2650,27 @@ static void Mod_Q1BSP_LoadLeafs(lump_t *l)
 
                out->contents = LittleLong(in->contents);
 
-               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)
+               out->firstleafsurface = loadmodel->brush.data_leafsurfaces + (unsigned short)LittleShort(in->firstmarksurface);
+               out->numleafsurfaces = (unsigned short)LittleShort(in->nummarksurfaces);
+               if ((unsigned short)LittleShort(in->firstmarksurface) + out->numleafsurfaces > loadmodel->brush.num_leafsurfaces)
                {
-                       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;
+                       Con_Printf("Mod_Q1BSP_LoadLeafs: invalid leafsurface range %i:%i outside range %i:%i\n", (int)(out->firstleafsurface - loadmodel->brush.data_leafsurfaces), (int)(out->firstleafsurface + out->numleafsurfaces - loadmodel->brush.data_leafsurfaces), 0, loadmodel->brush.num_leafsurfaces);
+                       out->firstleafsurface = NULL;
+                       out->numleafsurfaces = 0;
                }
 
-               out->pvsdata = pvs;
-               memset(out->pvsdata, 0xFF, pvschainbytes);
-               pvs += pvschainbytes;
+               out->clusterindex = i - 1;
+               if (out->clusterindex >= loadmodel->brush.num_pvsclusters)
+                       out->clusterindex = -1;
 
                p = LittleLong(in->visofs);
-               if (p >= 0)
+               // ignore visofs errors on leaf 0 (solid)
+               if (p >= 0 && out->clusterindex >= 0)
                {
                        if (p >= loadmodel->brushq1.num_compressedpvs)
-                               Con_Printf("Mod_Q1BSP_LoadLeafs: invalid visofs\n");
+                               Con_Print("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);
+                               Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, loadmodel->brush.data_pvsclusters + out->clusterindex * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (out->clusterindex + 1) * loadmodel->brush.num_pvsclusterbytes);
                }
 
                for (j = 0;j < 4;j++)
@@ -1860,98 +2680,73 @@ static void Mod_Q1BSP_LoadLeafs(lump_t *l)
        }
 }
 
-static void Mod_Q1BSP_LoadClipnodes(lump_t *l)
+qboolean Mod_Q1BSP_CheckWaterAlphaSupport(void)
+{
+       int i, j;
+       mleaf_t *leaf;
+       const unsigned char *pvs;
+       // if there's no vis data, assume supported (because everything is visible all the time)
+       if (!loadmodel->brush.data_pvsclusters)
+               return true;
+       // check all liquid leafs to see if they can see into empty leafs, if any
+       // can we can assume this map supports r_wateralpha
+       for (i = 0, leaf = loadmodel->brush.data_leafs;i < loadmodel->brush.num_leafs;i++, leaf++)
+       {
+               if ((leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME) && leaf->clusterindex >= 0)
+               {
+                       pvs = loadmodel->brush.data_pvsclusters + leaf->clusterindex * loadmodel->brush.num_pvsclusterbytes;
+                       for (j = 0;j < loadmodel->brush.num_leafs;j++)
+                               if (CHECKPVSBIT(pvs, loadmodel->brush.data_leafs[j].clusterindex) && loadmodel->brush.data_leafs[j].contents == CONTENTS_EMPTY)
+                                       return true;
+               }
+       }
+       return false;
+}
+
+static void Mod_Q1BSP_LoadClipnodes(lump_t *l, hullinfo_t *hullinfo)
 {
-       dclipnode_t *in, *out;
+       dclipnode_t *in;
+       mclipnode_t *out;
        int                     i, count;
        hull_t          *hull;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dclipnode_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadClipnodes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
+       out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out));
 
        loadmodel->brushq1.clipnodes = out;
        loadmodel->brushq1.numclipnodes = count;
 
-       if (loadmodel->brush.ishlbsp)
-       {
-               hull = &loadmodel->brushq1.hulls[1];
-               hull->clipnodes = out;
-               hull->firstclipnode = 0;
-               hull->lastclipnode = count-1;
-               hull->planes = loadmodel->brushq1.planes;
-               hull->clip_mins[0] = -16;
-               hull->clip_mins[1] = -16;
-               hull->clip_mins[2] = -36;
-               hull->clip_maxs[0] = 16;
-               hull->clip_maxs[1] = 16;
-               hull->clip_maxs[2] = 36;
-               VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size);
-
-               hull = &loadmodel->brushq1.hulls[2];
-               hull->clipnodes = out;
-               hull->firstclipnode = 0;
-               hull->lastclipnode = count-1;
-               hull->planes = loadmodel->brushq1.planes;
-               hull->clip_mins[0] = -32;
-               hull->clip_mins[1] = -32;
-               hull->clip_mins[2] = -32;
-               hull->clip_maxs[0] = 32;
-               hull->clip_maxs[1] = 32;
-               hull->clip_maxs[2] = 32;
-               VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size);
-
-               hull = &loadmodel->brushq1.hulls[3];
-               hull->clipnodes = out;
-               hull->firstclipnode = 0;
-               hull->lastclipnode = count-1;
-               hull->planes = loadmodel->brushq1.planes;
-               hull->clip_mins[0] = -16;
-               hull->clip_mins[1] = -16;
-               hull->clip_mins[2] = -18;
-               hull->clip_maxs[0] = 16;
-               hull->clip_maxs[1] = 16;
-               hull->clip_maxs[2] = 18;
-               VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size);
-       }
-       else
+       for (i = 1; i < MAX_MAP_HULLS; i++)
        {
-               hull = &loadmodel->brushq1.hulls[1];
-               hull->clipnodes = out;
-               hull->firstclipnode = 0;
-               hull->lastclipnode = count-1;
-               hull->planes = loadmodel->brushq1.planes;
-               hull->clip_mins[0] = -16;
-               hull->clip_mins[1] = -16;
-               hull->clip_mins[2] = -24;
-               hull->clip_maxs[0] = 16;
-               hull->clip_maxs[1] = 16;
-               hull->clip_maxs[2] = 32;
-               VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size);
-
-               hull = &loadmodel->brushq1.hulls[2];
+               hull = &loadmodel->brushq1.hulls[i];
                hull->clipnodes = out;
                hull->firstclipnode = 0;
                hull->lastclipnode = count-1;
-               hull->planes = loadmodel->brushq1.planes;
-               hull->clip_mins[0] = -32;
-               hull->clip_mins[1] = -32;
-               hull->clip_mins[2] = -24;
-               hull->clip_maxs[0] = 32;
-               hull->clip_maxs[1] = 32;
-               hull->clip_maxs[2] = 64;
+               hull->planes = loadmodel->brush.data_planes;
+               hull->clip_mins[0] = hullinfo->hullsizes[i][0][0];
+               hull->clip_mins[1] = hullinfo->hullsizes[i][0][1];
+               hull->clip_mins[2] = hullinfo->hullsizes[i][0][2];
+               hull->clip_maxs[0] = hullinfo->hullsizes[i][1][0];
+               hull->clip_maxs[1] = hullinfo->hullsizes[i][1][1];
+               hull->clip_maxs[2] = hullinfo->hullsizes[i][1][2];
                VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size);
        }
 
        for (i=0 ; i<count ; i++, out++, in++)
        {
                out->planenum = LittleLong(in->planenum);
-               out->children[0] = LittleShort(in->children[0]);
-               out->children[1] = LittleShort(in->children[1]);
-               if (out->children[0] >= count || out->children[1] >= count)
-                       Host_Error("Corrupt clipping hull(out of range child)\n");
+               // LordHavoc: this code supports arguire qbsp's broken clipnodes indices (more than 32768 clipnodes), values above count are assumed to be contents values
+               out->children[0] = (unsigned short)LittleShort(in->children[0]);
+               out->children[1] = (unsigned short)LittleShort(in->children[1]);
+               if (out->children[0] >= count)
+                       out->children[0] -= 65536;
+               if (out->children[1] >= count)
+                       out->children[1] -= 65536;
+               if (out->planenum < 0 || out->planenum >= loadmodel->brush.num_planes)
+                       Host_Error("Corrupt clipping hull(out of range planenum)");
        }
 }
 
@@ -1959,45 +2754,45 @@ static void Mod_Q1BSP_LoadClipnodes(lump_t *l)
 static void Mod_Q1BSP_MakeHull0(void)
 {
        mnode_t         *in;
-       dclipnode_t *out;
+       mclipnode_t *out;
        int                     i;
        hull_t          *hull;
 
        hull = &loadmodel->brushq1.hulls[0];
 
-       in = loadmodel->brushq1.nodes;
-       out = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numnodes * sizeof(dclipnode_t));
+       in = loadmodel->brush.data_nodes;
+       out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_nodes * sizeof(*out));
 
        hull->clipnodes = out;
        hull->firstclipnode = 0;
-       hull->lastclipnode = loadmodel->brushq1.numnodes - 1;
-       hull->planes = loadmodel->brushq1.planes;
+       hull->lastclipnode = loadmodel->brush.num_nodes - 1;
+       hull->planes = loadmodel->brush.data_planes;
 
-       for (i = 0;i < loadmodel->brushq1.numnodes;i++, out++, in++)
+       for (i = 0;i < loadmodel->brush.num_nodes;i++, out++, in++)
        {
-               out->planenum = in->plane - loadmodel->brushq1.planes;
-               out->children[0] = in->children[0]->contents < 0 ? in->children[0]->contents : in->children[0] - loadmodel->brushq1.nodes;
-               out->children[1] = in->children[1]->contents < 0 ? in->children[1]->contents : in->children[1] - loadmodel->brushq1.nodes;
+               out->planenum = in->plane - loadmodel->brush.data_planes;
+               out->children[0] = in->children[0]->plane ? in->children[0] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[0])->contents;
+               out->children[1] = in->children[1]->plane ? in->children[1] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[1])->contents;
        }
 }
 
-static void Mod_Q1BSP_LoadMarksurfaces(lump_t *l)
+static void Mod_Q1BSP_LoadLeaffaces(lump_t *l)
 {
        int i, j;
        short *in;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (short *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
-               Host_Error("Mod_Q1BSP_LoadMarksurfaces: funny lump size in %s",loadmodel->name);
-       loadmodel->brushq1.nummarksurfaces = l->filelen / sizeof(*in);
-       loadmodel->brushq1.marksurfaces = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.nummarksurfaces * sizeof(int));
+               Host_Error("Mod_Q1BSP_LoadLeaffaces: funny lump size in %s",loadmodel->name);
+       loadmodel->brush.num_leafsurfaces = l->filelen / sizeof(*in);
+       loadmodel->brush.data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafsurfaces * sizeof(int));
 
-       for (i = 0;i < loadmodel->brushq1.nummarksurfaces;i++)
+       for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++)
        {
-               j = (unsigned) LittleShort(in[i]);
-               if (j >= loadmodel->brushq1.numsurfaces)
-                       Host_Error("Mod_Q1BSP_LoadMarksurfaces: bad surface number");
-               loadmodel->brushq1.marksurfaces[i] = j;
+               j = (unsigned short) LittleShort(in[i]);
+               if (j >= loadmodel->num_surfaces)
+                       Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number");
+               loadmodel->brush.data_leafsurfaces[i] = j;
        }
 }
 
@@ -2006,11 +2801,11 @@ static void Mod_Q1BSP_LoadSurfedges(lump_t *l)
        int             i;
        int             *in;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (int *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadSurfedges: funny lump size in %s",loadmodel->name);
        loadmodel->brushq1.numsurfedges = l->filelen / sizeof(*in);
-       loadmodel->brushq1.surfedges = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numsurfedges * sizeof(int));
+       loadmodel->brushq1.surfedges = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numsurfedges * sizeof(int));
 
        for (i = 0;i < loadmodel->brushq1.numsurfedges;i++)
                loadmodel->brushq1.surfedges[i] = LittleLong(in[i]);
@@ -2023,14 +2818,14 @@ static void Mod_Q1BSP_LoadPlanes(lump_t *l)
        mplane_t        *out;
        dplane_t        *in;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (dplane_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q1BSP_LoadPlanes: funny lump size in %s", loadmodel->name);
 
-       loadmodel->brushq1.numplanes = l->filelen / sizeof(*in);
-       loadmodel->brushq1.planes = out = Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numplanes * sizeof(*out));
+       loadmodel->brush.num_planes = l->filelen / sizeof(*in);
+       loadmodel->brush.data_planes = out = (mplane_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_planes * sizeof(*out));
 
-       for (i = 0;i < loadmodel->brushq1.numplanes;i++, in++, out++)
+       for (i = 0;i < loadmodel->brush.num_planes;i++, in++, out++)
        {
                out->normal[0] = LittleFloat(in->normal[0]);
                out->normal[1] = LittleFloat(in->normal[1]);
@@ -2041,12 +2836,111 @@ static void Mod_Q1BSP_LoadPlanes(lump_t *l)
        }
 }
 
+static void Mod_Q1BSP_LoadMapBrushes(void)
+{
+#if 0
+// unfinished
+       int submodel, numbrushes;
+       qboolean firstbrush;
+       char *text, *maptext;
+       char mapfilename[MAX_QPATH];
+       FS_StripExtension (loadmodel->name, mapfilename, sizeof (mapfilename));
+       strlcat (mapfilename, ".map", sizeof (mapfilename));
+       maptext = (unsigned char*) FS_LoadFile(mapfilename, tempmempool, false, NULL);
+       if (!maptext)
+               return;
+       text = maptext;
+       if (!COM_ParseToken_Simple(&data, false, false))
+               return; // error
+       submodel = 0;
+       for (;;)
+       {
+               if (!COM_ParseToken_Simple(&data, false, false))
+                       break;
+               if (com_token[0] != '{')
+                       return; // error
+               // entity
+               firstbrush = true;
+               numbrushes = 0;
+               maxbrushes = 256;
+               brushes = Mem_Alloc(loadmodel->mempool, maxbrushes * sizeof(mbrush_t));
+               for (;;)
+               {
+                       if (!COM_ParseToken_Simple(&data, false, false))
+                               return; // error
+                       if (com_token[0] == '}')
+                               break; // end of entity
+                       if (com_token[0] == '{')
+                       {
+                               // brush
+                               if (firstbrush)
+                               {
+                                       if (submodel)
+                                       {
+                                               if (submodel > loadmodel->brush.numsubmodels)
+                                               {
+                                                       Con_Printf("Mod_Q1BSP_LoadMapBrushes: .map has more submodels than .bsp!\n");
+                                                       model = NULL;
+                                               }
+                                               else
+                                                       model = loadmodel->brush.submodels[submodel];
+                                       }
+                                       else
+                                               model = loadmodel;
+                               }
+                               for (;;)
+                               {
+                                       if (!COM_ParseToken_Simple(&data, false, false))
+                                               return; // error
+                                       if (com_token[0] == '}')
+                                               break; // end of brush
+                                       // each brush face should be this format:
+                                       // ( x y z ) ( x y z ) ( x y z ) texture scroll_s scroll_t rotateangle scale_s scale_t
+                                       // FIXME: support hl .map format
+                                       for (pointnum = 0;pointnum < 3;pointnum++)
+                                       {
+                                               COM_ParseToken_Simple(&data, false, false);
+                                               for (componentnum = 0;componentnum < 3;componentnum++)
+                                               {
+                                                       COM_ParseToken_Simple(&data, false, false);
+                                                       point[pointnum][componentnum] = atof(com_token);
+                                               }
+                                               COM_ParseToken_Simple(&data, false, false);
+                                       }
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       strlcpy(facetexture, com_token, sizeof(facetexture));
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       //scroll_s = atof(com_token);
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       //scroll_t = atof(com_token);
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       //rotate = atof(com_token);
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       //scale_s = atof(com_token);
+                                       COM_ParseToken_Simple(&data, false, false);
+                                       //scale_t = atof(com_token);
+                                       TriangleNormal(point[0], point[1], point[2], planenormal);
+                                       VectorNormalizeDouble(planenormal);
+                                       planedist = DotProduct(point[0], planenormal);
+                                       //ChooseTexturePlane(planenormal, texturevector[0], texturevector[1]);
+                               }
+                               continue;
+                       }
+               }
+       }
+#endif
+}
+
+
+#define MAX_PORTALPOINTS 64
+
 typedef struct portal_s
 {
        mplane_t plane;
        mnode_t *nodes[2];              // [0] = front side of plane
        struct portal_s *next[2];
-       winding_t *winding;
+       int numpoints;
+       double points[3*MAX_PORTALPOINTS];
        struct portal_s *chain; // all portals are linked into a list
 }
 portal_t;
@@ -2061,7 +2955,7 @@ AllocPortal
 static portal_t *AllocPortal(void)
 {
        portal_t *p;
-       p = Mem_Alloc(loadmodel->mempool, sizeof(portal_t));
+       p = (portal_t *)Mem_Alloc(loadmodel->mempool, sizeof(portal_t));
        p->chain = portalchain;
        portalchain = p;
        return p;
@@ -2074,11 +2968,13 @@ static void FreePortal(portal_t *p)
 
 static void Mod_Q1BSP_RecursiveRecalcNodeBBox(mnode_t *node)
 {
+       // process only nodes (leafs already had their box calculated)
+       if (!node->plane)
+               return;
+
        // calculate children first
-       if (node->children[0]->contents >= 0)
-               Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[0]);
-       if (node->children[1]->contents >= 0)
-               Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[1]);
+       Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[0]);
+       Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[1]);
 
        // make combined bounding box from children
        node->mins[0] = min(node->children[0]->mins[0], node->children[1]->mins[0]);
@@ -2096,85 +2992,55 @@ static void Mod_Q1BSP_FinalizePortals(void)
        mportal_t *portal;
        mvertex_t *point;
        mleaf_t *leaf, *endleaf;
-       winding_t *w;
 
-       // recalculate bounding boxes for all leafs(because qbsp is very sloppy)
-       leaf = loadmodel->brushq1.leafs;
-       endleaf = leaf + loadmodel->brushq1.numleafs;
+       // tally up portal and point counts and recalculate bounding boxes for all
+       // leafs (because qbsp is very sloppy)
+       leaf = loadmodel->brush.data_leafs;
+       endleaf = leaf + loadmodel->brush.num_leafs;
        for (;leaf < endleaf;leaf++)
        {
                VectorSet(leaf->mins,  2000000000,  2000000000,  2000000000);
                VectorSet(leaf->maxs, -2000000000, -2000000000, -2000000000);
        }
        p = portalchain;
-       while (p)
-       {
-               if (p->winding)
-               {
-                       for (i = 0;i < 2;i++)
-                       {
-                               leaf = (mleaf_t *)p->nodes[i];
-                               w = p->winding;
-                               for (j = 0;j < w->numpoints;j++)
-                               {
-                                       if (leaf->mins[0] > w->points[j][0]) leaf->mins[0] = w->points[j][0];
-                                       if (leaf->mins[1] > w->points[j][1]) leaf->mins[1] = w->points[j][1];
-                                       if (leaf->mins[2] > w->points[j][2]) leaf->mins[2] = w->points[j][2];
-                                       if (leaf->maxs[0] < w->points[j][0]) leaf->maxs[0] = w->points[j][0];
-                                       if (leaf->maxs[1] < w->points[j][1]) leaf->maxs[1] = w->points[j][1];
-                                       if (leaf->maxs[2] < w->points[j][2]) leaf->maxs[2] = w->points[j][2];
-                               }
-                       }
-               }
-               p = p->chain;
-       }
-
-       Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brushq1.nodes);
-
-       // tally up portal and point counts
-       p = portalchain;
        numportals = 0;
        numpoints = 0;
        while (p)
        {
                // note: this check must match the one below or it will usually corrupt memory
                // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides
-               if (p->winding && p->nodes[0] != p->nodes[1]
-                && p->nodes[0]->contents != CONTENTS_SOLID && p->nodes[1]->contents != CONTENTS_SOLID
-                && p->nodes[0]->contents != CONTENTS_SKY && p->nodes[1]->contents != CONTENTS_SKY)
+               if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1] && ((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0)
                {
                        numportals += 2;
-                       numpoints += p->winding->numpoints * 2;
+                       numpoints += p->numpoints * 2;
                }
                p = p->chain;
        }
-       loadmodel->brushq1.portals = Mem_Alloc(loadmodel->mempool, numportals * sizeof(mportal_t) + numpoints * sizeof(mvertex_t));
-       loadmodel->brushq1.numportals = numportals;
-       loadmodel->brushq1.portalpoints = (void *)((qbyte *) loadmodel->brushq1.portals + numportals * sizeof(mportal_t));
-       loadmodel->brushq1.numportalpoints = numpoints;
+       loadmodel->brush.data_portals = (mportal_t *)Mem_Alloc(loadmodel->mempool, numportals * sizeof(mportal_t) + numpoints * sizeof(mvertex_t));
+       loadmodel->brush.num_portals = numportals;
+       loadmodel->brush.data_portalpoints = (mvertex_t *)((unsigned char *) loadmodel->brush.data_portals + numportals * sizeof(mportal_t));
+       loadmodel->brush.num_portalpoints = numpoints;
        // clear all leaf portal chains
-       for (i = 0;i < loadmodel->brushq1.numleafs;i++)
-               loadmodel->brushq1.leafs[i].portals = NULL;
+       for (i = 0;i < loadmodel->brush.num_leafs;i++)
+               loadmodel->brush.data_leafs[i].portals = NULL;
        // process all portals in the global portal chain, while freeing them
-       portal = loadmodel->brushq1.portals;
-       point = loadmodel->brushq1.portalpoints;
+       portal = loadmodel->brush.data_portals;
+       point = loadmodel->brush.data_portalpoints;
        p = portalchain;
        portalchain = NULL;
        while (p)
        {
                pnext = p->chain;
 
-               if (p->winding)
+               if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1])
                {
                        // note: this check must match the one above or it will usually corrupt memory
                        // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides
-                       if (p->nodes[0] != p->nodes[1]
-                        && p->nodes[0]->contents != CONTENTS_SOLID && p->nodes[1]->contents != CONTENTS_SOLID
-                        && p->nodes[0]->contents != CONTENTS_SKY && p->nodes[1]->contents != CONTENTS_SKY)
+                       if (((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0)
                        {
                                // first make the back to front portal(forward portal)
                                portal->points = point;
-                               portal->numpoints = p->winding->numpoints;
+                               portal->numpoints = p->numpoints;
                                portal->plane.dist = p->plane.dist;
                                VectorCopy(p->plane.normal, portal->plane.normal);
                                portal->here = (mleaf_t *)p->nodes[1];
@@ -2182,9 +3048,10 @@ static void Mod_Q1BSP_FinalizePortals(void)
                                // copy points
                                for (j = 0;j < portal->numpoints;j++)
                                {
-                                       VectorCopy(p->winding->points[j], point->position);
+                                       VectorCopy(p->points + j*3, point->position);
                                        point++;
                                }
+                               BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position);
                                PlaneClassify(&portal->plane);
 
                                // link into leaf's portal chain
@@ -2196,7 +3063,7 @@ static void Mod_Q1BSP_FinalizePortals(void)
 
                                // then make the front to back portal(backward portal)
                                portal->points = point;
-                               portal->numpoints = p->winding->numpoints;
+                               portal->numpoints = p->numpoints;
                                portal->plane.dist = -p->plane.dist;
                                VectorNegate(p->plane.normal, portal->plane.normal);
                                portal->here = (mleaf_t *)p->nodes[0];
@@ -2204,9 +3071,10 @@ static void Mod_Q1BSP_FinalizePortals(void)
                                // copy points
                                for (j = portal->numpoints - 1;j >= 0;j--)
                                {
-                                       VectorCopy(p->winding->points[j], point->position);
+                                       VectorCopy(p->points + j*3, point->position);
                                        point++;
                                }
+                               BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position);
                                PlaneClassify(&portal->plane);
 
                                // link into leaf's portal chain
@@ -2216,11 +3084,26 @@ static void Mod_Q1BSP_FinalizePortals(void)
                                // advance to next portal
                                portal++;
                        }
-                       Winding_Free(p->winding);
+                       // add the portal's polygon points to the leaf bounding boxes
+                       for (i = 0;i < 2;i++)
+                       {
+                               leaf = (mleaf_t *)p->nodes[i];
+                               for (j = 0;j < p->numpoints;j++)
+                               {
+                                       if (leaf->mins[0] > p->points[j*3+0]) leaf->mins[0] = p->points[j*3+0];
+                                       if (leaf->mins[1] > p->points[j*3+1]) leaf->mins[1] = p->points[j*3+1];
+                                       if (leaf->mins[2] > p->points[j*3+2]) leaf->mins[2] = p->points[j*3+2];
+                                       if (leaf->maxs[0] < p->points[j*3+0]) leaf->maxs[0] = p->points[j*3+0];
+                                       if (leaf->maxs[1] < p->points[j*3+1]) leaf->maxs[1] = p->points[j*3+1];
+                                       if (leaf->maxs[2] < p->points[j*3+2]) leaf->maxs[2] = p->points[j*3+2];
+                               }
+                       }
                }
                FreePortal(p);
                p = pnext;
        }
+       // now recalculate the node bounding boxes from the leafs
+       Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes);
 }
 
 /*
@@ -2265,7 +3148,7 @@ static void RemovePortalFromNodes(portal_t *portal)
                portalpointer = (void **) &node->portals;
                while (1)
                {
-                       t = *portalpointer;
+                       t = (portal_t *)*portalpointer;
                        if (!t)
                                Host_Error("RemovePortalFromNodes: portal not in leaf");
 
@@ -2296,16 +3179,18 @@ static void RemovePortalFromNodes(portal_t *portal)
        }
 }
 
+#define PORTAL_DIST_EPSILON (1.0 / 32.0)
 static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
 {
-       int side;
+       int i, side;
        mnode_t *front, *back, *other_node;
        mplane_t clipplane, *plane;
        portal_t *portal, *nextportal, *nodeportal, *splitportal, *temp;
-       winding_t *nodeportalwinding, *frontwinding, *backwinding;
+       int numfrontpoints, numbackpoints;
+       double frontpoints[3*MAX_PORTALPOINTS], backpoints[3*MAX_PORTALPOINTS];
 
        // if a leaf, we're done
-       if (node->contents)
+       if (!node->plane)
                return;
 
        plane = node->plane;
@@ -2320,7 +3205,9 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
        nodeportal = AllocPortal();
        nodeportal->plane = *plane;
 
-       nodeportalwinding = Winding_NewFromPlane(nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist);
+       // TODO: calculate node bounding boxes during recursion and calculate a maximum plane size accordingly to improve precision (as most maps do not need 1 billion unit plane polygons)
+       PolygonD_QuadForPlane(nodeportal->points, nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist, 1024.0*1024.0*1024.0);
+       nodeportal->numpoints = 4;
        side = 0;       // shut up compiler warning
        for (portal = (portal_t *)node->portals;portal;portal = portal->next[side])
        {
@@ -2338,21 +3225,26 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                else
                        Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal");
 
-               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");
+               for (i = 0;i < nodeportal->numpoints*3;i++)
+                       frontpoints[i] = nodeportal->points[i];
+               PolygonD_Divide(nodeportal->numpoints, frontpoints, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, nodeportal->points, &nodeportal->numpoints, 0, NULL, NULL, NULL);
+               if (nodeportal->numpoints <= 0 || nodeportal->numpoints >= MAX_PORTALPOINTS)
                        break;
-               }
        }
 
-       if (nodeportalwinding)
+       if (nodeportal->numpoints < 3)
        {
-               // if the plane was not clipped on all sides, there was an error
-               nodeportal->winding = nodeportalwinding;
-               AddPortalToNodes(nodeportal, front, back);
+               Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n");
+               nodeportal->numpoints = 0;
+       }
+       else if (nodeportal->numpoints >= MAX_PORTALPOINTS)
+       {
+               Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal has too many points\n");
+               nodeportal->numpoints = 0;
        }
 
+       AddPortalToNodes(nodeportal, front, back);
+
        // split the portals of this node along this node's plane and assign them to the children of this node
        // (migrating the portals downward through the tree)
        for (portal = (portal_t *)node->portals;portal;portal = nextportal)
@@ -2366,14 +3258,16 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                else
                        Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal");
                nextportal = portal->next[side];
+               if (!portal->numpoints)
+                       continue;
 
                other_node = portal->nodes[!side];
                RemovePortalFromNodes(portal);
 
                // cut the portal into two portals, one on each side of the node plane
-               Winding_Divide(portal->winding, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, &frontwinding, &backwinding);
+               PolygonD_Divide(portal->numpoints, portal->points, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, frontpoints, &numfrontpoints, MAX_PORTALPOINTS, backpoints, &numbackpoints, NULL);
 
-               if (!frontwinding)
+               if (!numfrontpoints)
                {
                        if (side == 0)
                                AddPortalToNodes(portal, back, other_node);
@@ -2381,7 +3275,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                                AddPortalToNodes(portal, other_node, back);
                        continue;
                }
-               if (!backwinding)
+               if (!numbackpoints)
                {
                        if (side == 0)
                                AddPortalToNodes(portal, front, other_node);
@@ -2390,14 +3284,17 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
                        continue;
                }
 
-               // the winding is split
+               // the portal is split
                splitportal = AllocPortal();
                temp = splitportal->chain;
                *splitportal = *portal;
                splitportal->chain = temp;
-               splitportal->winding = backwinding;
-               Winding_Free(portal->winding);
-               portal->winding = frontwinding;
+               for (i = 0;i < numbackpoints*3;i++)
+                       splitportal->points[i] = backpoints[i];
+               splitportal->numpoints = numbackpoints;
+               for (i = 0;i < numfrontpoints*3;i++)
+                       portal->points[i] = frontpoints[i];
+               portal->numpoints = numfrontpoints;
 
                if (side == 0)
                {
@@ -2418,153 +3315,29 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
 static void Mod_Q1BSP_MakePortals(void)
 {
        portalchain = NULL;
-       Mod_Q1BSP_RecursiveNodePortals(loadmodel->brushq1.nodes);
+       Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes);
        Mod_Q1BSP_FinalizePortals();
 }
 
-static void Mod_Q1BSP_BuildSurfaceNeighbors(msurface_t *surfaces, int numsurfaces, mempool_t *mempool)
-{
-#if 0
-       int surfnum, vertnum, vertnum2, snum, vnum, vnum2;
-       msurface_t *surf, *s;
-       float *v0, *v1, *v2, *v3;
-       for (surf = surfaces, surfnum = 0;surfnum < numsurfaces;surf++, surfnum++)
-               surf->neighborsurfaces = Mem_Alloc(mempool, surf->poly_numverts * sizeof(msurface_t *));
-       for (surf = surfaces, surfnum = 0;surfnum < numsurfaces;surf++, surfnum++)
-       {
-               for (vertnum = surf->poly_numverts - 1, vertnum2 = 0, v0 = surf->poly_verts + (surf->poly_numverts - 1) * 3, v1 = surf->poly_verts;vertnum2 < surf->poly_numverts;vertnum = vertnum2, vertnum2++, v0 = v1, v1 += 3)
-               {
-                       if (surf->neighborsurfaces[vertnum])
-                               continue;
-                       surf->neighborsurfaces[vertnum] = NULL;
-                       for (s = surfaces, snum = 0;snum < numsurfaces;s++, snum++)
-                       {
-                               if (s->poly_mins[0] > (surf->poly_maxs[0] + 1) || s->poly_maxs[0] < (surf->poly_mins[0] - 1)
-                                || s->poly_mins[1] > (surf->poly_maxs[1] + 1) || s->poly_maxs[1] < (surf->poly_mins[1] - 1)
-                                || s->poly_mins[2] > (surf->poly_maxs[2] + 1) || s->poly_maxs[2] < (surf->poly_mins[2] - 1)
-                                || s == surf)
-                                       continue;
-                               for (vnum = 0;vnum < s->poly_numverts;vnum++)
-                                       if (s->neighborsurfaces[vnum] == surf)
-                                               break;
-                               if (vnum < s->poly_numverts)
-                                       continue;
-                               for (vnum = s->poly_numverts - 1, vnum2 = 0, v2 = s->poly_verts + (s->poly_numverts - 1) * 3, v3 = s->poly_verts;vnum2 < s->poly_numverts;vnum = vnum2, vnum2++, v2 = v3, v3 += 3)
-                               {
-                                       if (s->neighborsurfaces[vnum] == NULL
-                                        && ((v0[0] == v2[0] && v0[1] == v2[1] && v0[2] == v2[2] && v1[0] == v3[0] && v1[1] == v3[1] && v1[2] == v3[2])
-                                         || (v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2] && v0[0] == v3[0] && v0[1] == v3[1] && v0[2] == v3[2])))
-                                       {
-                                               surf->neighborsurfaces[vertnum] = s;
-                                               s->neighborsurfaces[vnum] = surf;
-                                               break;
-                                       }
-                               }
-                               if (vnum < s->poly_numverts)
-                                       break;
-                       }
-               }
-       }
-#endif
-}
-
-static void Mod_Q1BSP_BuildLightmapUpdateChains(mempool_t *mempool, model_t *model)
-{
-       int i, j, stylecounts[256], totalcount, remapstyles[256];
-       msurface_t *surf;
-       memset(stylecounts, 0, sizeof(stylecounts));
-       for (i = 0;i < model->brushq1.nummodelsurfaces;i++)
-       {
-               surf = model->brushq1.surfaces + model->brushq1.firstmodelsurface + i;
-               for (j = 0;j < MAXLIGHTMAPS;j++)
-                       stylecounts[surf->styles[j]]++;
-       }
-       totalcount = 0;
-       model->brushq1.light_styles = 0;
-       for (i = 0;i < 255;i++)
-       {
-               if (stylecounts[i])
-               {
-                       remapstyles[i] = model->brushq1.light_styles++;
-                       totalcount += stylecounts[i] + 1;
-               }
-       }
-       if (!totalcount)
-               return;
-       model->brushq1.light_style = Mem_Alloc(mempool, model->brushq1.light_styles * sizeof(qbyte));
-       model->brushq1.light_stylevalue = Mem_Alloc(mempool, model->brushq1.light_styles * sizeof(int));
-       model->brushq1.light_styleupdatechains = Mem_Alloc(mempool, model->brushq1.light_styles * sizeof(msurface_t **));
-       model->brushq1.light_styleupdatechainsbuffer = Mem_Alloc(mempool, totalcount * sizeof(msurface_t *));
-       model->brushq1.light_styles = 0;
-       for (i = 0;i < 255;i++)
-               if (stylecounts[i])
-                       model->brushq1.light_style[model->brushq1.light_styles++] = i;
-       j = 0;
-       for (i = 0;i < model->brushq1.light_styles;i++)
-       {
-               model->brushq1.light_styleupdatechains[i] = model->brushq1.light_styleupdatechainsbuffer + j;
-               j += stylecounts[model->brushq1.light_style[i]] + 1;
-       }
-       for (i = 0;i < model->brushq1.nummodelsurfaces;i++)
-       {
-               surf = model->brushq1.surfaces + model->brushq1.firstmodelsurface + i;
-               for (j = 0;j < MAXLIGHTMAPS;j++)
-                       if (surf->styles[j] != 255)
-                               *model->brushq1.light_styleupdatechains[remapstyles[surf->styles[j]]]++ = surf;
-       }
-       j = 0;
-       for (i = 0;i < model->brushq1.light_styles;i++)
-       {
-               *model->brushq1.light_styleupdatechains[i] = NULL;
-               model->brushq1.light_styleupdatechains[i] = model->brushq1.light_styleupdatechainsbuffer + j;
-               j += stylecounts[model->brushq1.light_style[i]] + 1;
-       }
-}
-
-static void Mod_Q1BSP_BuildPVSTextureChains(model_t *model)
+//Returns PVS data for a given point
+//(note: can return NULL)
+static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p)
 {
-       int i, j;
-       for (i = 0;i < model->brushq1.numtextures;i++)
-               model->brushq1.pvstexturechainslength[i] = 0;
-       for (i = 0, j = model->brushq1.firstmodelsurface;i < model->brushq1.nummodelsurfaces;i++, j++)
-       {
-               if (model->brushq1.surfacepvsframes[j] == model->brushq1.pvsframecount)
-               {
-                       model->brushq1.pvssurflist[model->brushq1.pvssurflistlength++] = j;
-                       model->brushq1.pvstexturechainslength[model->brushq1.surfaces[j].texinfo->texture->number]++;
-               }
-       }
-       for (i = 0, j = 0;i < model->brushq1.numtextures;i++)
-       {
-               if (model->brushq1.pvstexturechainslength[i])
-               {
-                       model->brushq1.pvstexturechains[i] = model->brushq1.pvstexturechainsbuffer + j;
-                       j += model->brushq1.pvstexturechainslength[i] + 1;
-               }
-               else
-                       model->brushq1.pvstexturechains[i] = NULL;
-       }
-       for (i = 0, j = model->brushq1.firstmodelsurface;i < model->brushq1.nummodelsurfaces;i++, j++)
-               if (model->brushq1.surfacepvsframes[j] == model->brushq1.pvsframecount)
-                       *model->brushq1.pvstexturechains[model->brushq1.surfaces[j].texinfo->texture->number]++ = model->brushq1.surfaces + j;
-       for (i = 0;i < model->brushq1.numtextures;i++)
-       {
-               if (model->brushq1.pvstexturechainslength[i])
-               {
-                       *model->brushq1.pvstexturechains[i] = NULL;
-                       model->brushq1.pvstexturechains[i] -= model->brushq1.pvstexturechainslength[i];
-               }
-       }
+       mnode_t *node;
+       node = model->brush.data_nodes;
+       while (node->plane)
+               node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist];
+       if (((mleaf_t *)node)->clusterindex >= 0)
+               return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes;
+       else
+               return NULL;
 }
 
-static 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(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbytes, mnode_t *node)
 {
-       int i;
-       float d;
-
-       while (node->contents >= 0)
+       while (node->plane)
        {
-               d = PlaneDiff(org, node->plane);
+               float d = PlaneDiff(org, node->plane);
                if (d > radius)
                        node = node->children[0];
                else if (d < -radius)
@@ -2576,39 +3349,34 @@ static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(model_t *model, const vec3_t org,
                        node = node->children[1];
                }
        }
-       // FIXME: code!
-       // if this is a leaf, accumulate the pvs bits
-       if (node->contents != CONTENTS_SOLID && ((mleaf_t *)node)->pvsdata)
+       // if this leaf is in a cluster, accumulate the pvs bits
+       if (((mleaf_t *)node)->clusterindex >= 0)
+       {
+               int i;
+               unsigned char *pvs = model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes;
                for (i = 0;i < pvsbytes;i++)
-                       pvsbuffer[i] |= ((mleaf_t *)node)->pvsdata[i];
+                       pvsbuffer[i] |= pvs[i];
+       }
 }
 
 //Calculates a PVS that is the inclusive or of all leafs within radius pixels
 //of the given point.
-static int Mod_Q1BSP_FatPVS(model_t *model, const vec3_t org, vec_t radius, qbyte *pvsbuffer, int pvsbufferlength)
+static int Mod_Q1BSP_FatPVS(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge)
 {
-       int bytes = ((model->brushq1.numleafs - 1) + 7) >> 3;
+       int bytes = model->brush.num_pvsclusterbytes;
        bytes = min(bytes, pvsbufferlength);
-       memset(pvsbuffer, 0, bytes);
-       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brushq1.nodes);
+       if (r_novis.integer || !model->brush.num_pvsclusters || !Mod_Q1BSP_GetPVS(model, org))
+       {
+               memset(pvsbuffer, 0xFF, bytes);
+               return bytes;
+       }
+       if (!merge)
+               memset(pvsbuffer, 0, bytes);
+       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_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)
+static void Mod_Q1BSP_RoundUpToHullSize(dp_model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs)
 {
        vec3_t size;
        const hull_t *hull;
@@ -2641,61 +3409,101 @@ static void Mod_Q1BSP_RoundUpToHullSize(model_t *cmodel, const vec3_t inmins, co
        VectorAdd(inmins, hull->clip_size, outmaxs);
 }
 
-extern void R_Model_Brush_DrawSky(entity_render_t *ent);
-extern void R_Model_Brush_Draw(entity_render_t *ent);
-extern void R_Model_Brush_DrawShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, float lightradius);
-extern void R_Model_Brush_DrawLight(entity_render_t *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_Q1BSP_Load(model_t *mod, void *buffer)
+void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 {
        int i, j, k;
        dheader_t *header;
        dmodel_t *bm;
        mempool_t *mainmempool;
-       char *loadname;
-       model_t *originalloadmodel;
-       float dist, modelyawradius, modelradius, *vec;
-       msurface_t *surf;
+       float dist, modelyawradius, modelradius;
+       msurface_t *surface;
+       int numshadowmeshtriangles;
+       hullinfo_t hullinfo;
+       int totalstylesurfaces, totalstyles, stylecounts[256], remapstyles[256];
+       model_brush_lightstyleinfo_t styleinfo[256];
+       unsigned char *datapointer;
+
+       mod->modeldatatypestring = "Q1BSP";
 
-       mod->type = mod_brush;
+       mod->type = mod_brushq1;
 
        header = (dheader_t *)buffer;
 
        i = LittleLong(header->version);
        if (i != BSPVERSION && i != 30)
-               Host_Error("Mod_Q1BSP_Load: %s has wrong version number(%i should be %i(Quake) or 30(HalfLife))", mod->name, i, BSPVERSION);
+               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;
 
+// fill in hull info
+       VectorClear (hullinfo.hullsizes[0][0]);
+       VectorClear (hullinfo.hullsizes[0][1]);
+       if (mod->brush.ishlbsp)
+       {
+               mod->modeldatatypestring = "HLBSP";
+
+               hullinfo.filehulls = 4;
+               VectorSet (hullinfo.hullsizes[1][0], -16, -16, -36);
+               VectorSet (hullinfo.hullsizes[1][1], 16, 16, 36);
+               VectorSet (hullinfo.hullsizes[2][0], -32, -32, -32);
+               VectorSet (hullinfo.hullsizes[2][1], 32, 32, 32);
+               VectorSet (hullinfo.hullsizes[3][0], -16, -16, -18);
+               VectorSet (hullinfo.hullsizes[3][1], 16, 16, 18);
+       }
+       else
+       {
+               hullinfo.filehulls = 4;
+               VectorSet (hullinfo.hullsizes[1][0], -16, -16, -24);
+               VectorSet (hullinfo.hullsizes[1][1], 16, 16, 32);
+               VectorSet (hullinfo.hullsizes[2][0], -32, -32, -24);
+               VectorSet (hullinfo.hullsizes[2][1], 32, 32, 64);
+       }
+
+// read lumps
+       mod_base = (unsigned char*)buffer;
+       for (i = 0; i < HEADER_LUMPS; i++)
+       {
+               header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs);
+               header->lumps[i].filelen = LittleLong(header->lumps[i].filelen);
+       }
+
        mod->soundfromcenter = true;
        mod->TraceBox = Mod_Q1BSP_TraceBox;
+       mod->PointSuperContents = Mod_Q1BSP_PointSuperContents;
+       mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight;
        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.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
-       mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize;
-       mod->brushq1.PointInLeaf = Mod_Q1BSP_PointInLeaf;
-       mod->brushq1.BuildPVSTextureChains = Mod_Q1BSP_BuildPVSTextureChains;
-
-       if (loadmodel->isworldmodel)
-       {
-               Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
-               // until we get a texture for it...
-               R_ResetQuakeSky();
-       }
-
-// swap all the lumps
-       mod_base = (qbyte *)header;
-
-       for (i = 0;i < (int) sizeof(dheader_t) / 4;i++)
-               ((int *)header)[i] = LittleLong(((int *)header)[i]);
+       mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS;
+       mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS;
+       mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs;
+       mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters;
+       mod->brush.LightPoint = Mod_Q1BSP_LightPoint;
+       mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
+       mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
+       mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize;
+       mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf;
+       mod->Draw = R_Q1BSP_Draw;
+       mod->DrawDepth = R_Q1BSP_DrawDepth;
+       mod->DrawDebug = R_Q1BSP_DrawDebug;
+       mod->GetLightInfo = R_Q1BSP_GetLightInfo;
+       mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+       mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+       mod->DrawLight = R_Q1BSP_DrawLight;
 
 // load into heap
 
-       // store which lightmap format to use
-       mod->brushq1.lightmaprgba = r_lightmaprgba.integer;
+       mod->brush.qw_md4sum = 0;
+       mod->brush.qw_md4sum2 = 0;
+       for (i = 0;i < HEADER_LUMPS;i++)
+       {
+               if (i == LUMP_ENTITIES)
+                       continue;
+               mod->brush.qw_md4sum ^= LittleLong(Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen));
+               if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES)
+                       continue;
+               mod->brush.qw_md4sum2 ^= LittleLong(Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen));
+       }
 
        Mod_Q1BSP_LoadEntities(&header->lumps[LUMP_ENTITIES]);
        Mod_Q1BSP_LoadVertexes(&header->lumps[LUMP_VERTEXES]);
@@ -2706,12 +3514,16 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
        Mod_Q1BSP_LoadPlanes(&header->lumps[LUMP_PLANES]);
        Mod_Q1BSP_LoadTexinfo(&header->lumps[LUMP_TEXINFO]);
        Mod_Q1BSP_LoadFaces(&header->lumps[LUMP_FACES]);
-       Mod_Q1BSP_LoadMarksurfaces(&header->lumps[LUMP_MARKSURFACES]);
+       Mod_Q1BSP_LoadLeaffaces(&header->lumps[LUMP_MARKSURFACES]);
        Mod_Q1BSP_LoadVisibility(&header->lumps[LUMP_VISIBILITY]);
+       // load submodels before leafs because they contain the number of vis leafs
+       Mod_Q1BSP_LoadSubmodels(&header->lumps[LUMP_MODELS], &hullinfo);
        Mod_Q1BSP_LoadLeafs(&header->lumps[LUMP_LEAFS]);
        Mod_Q1BSP_LoadNodes(&header->lumps[LUMP_NODES]);
-       Mod_Q1BSP_LoadClipnodes(&header->lumps[LUMP_CLIPNODES]);
-       Mod_Q1BSP_LoadSubmodels(&header->lumps[LUMP_MODELS]);
+       Mod_Q1BSP_LoadClipnodes(&header->lumps[LUMP_CLIPNODES], &hullinfo);
+
+       // check if the map supports transparent water rendering
+       loadmodel->brush.supportwateralpha = Mod_Q1BSP_CheckWaterAlphaSupport();
 
        if (mod->brushq1.data_compressedpvs)
                Mem_Free(mod->brushq1.data_compressedpvs);
@@ -2721,22 +3533,95 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
        Mod_Q1BSP_MakeHull0();
        Mod_Q1BSP_MakePortals();
 
-       if (developer.integer)
-               Con_Printf("Some stats for q1bsp model \"%s\": %i faces, %i nodes, %i leafs, %i visleafs, %i visleafportals\n", loadmodel->name, loadmodel->brushq1.numsurfaces, loadmodel->brushq1.numnodes, loadmodel->brushq1.numleafs, loadmodel->brushq1.numleafs - 1, loadmodel->brushq1.numportals);
-
        mod->numframes = 2;             // regular and alternate animation
+       mod->numskins = 1;
 
        mainmempool = mod->mempool;
-       loadname = mod->name;
 
-       Mod_Q1BSP_LoadLightList();
-       originalloadmodel = loadmodel;
-
-//
-// set up the submodels(FIXME: this is confusing)
-//
+       // make a single combined shadow mesh to allow optimized shadow volume creation
+       numshadowmeshtriangles = 0;
+       for (j = 0, surface = loadmodel->data_surfaces;j < loadmodel->num_surfaces;j++, surface++)
+       {
+               surface->num_firstshadowmeshtriangle = numshadowmeshtriangles;
+               numshadowmeshtriangles += surface->num_triangles;
+       }
+       loadmodel->brush.shadowmesh = Mod_ShadowMesh_Begin(loadmodel->mempool, numshadowmeshtriangles * 3, numshadowmeshtriangles, NULL, NULL, NULL, false, false, true);
+       for (j = 0, surface = loadmodel->data_surfaces;j < loadmodel->num_surfaces;j++, surface++)
+               Mod_ShadowMesh_AddMesh(loadmodel->mempool, loadmodel->brush.shadowmesh, NULL, NULL, NULL, loadmodel->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+       loadmodel->brush.shadowmesh = Mod_ShadowMesh_Finish(loadmodel->mempool, loadmodel->brush.shadowmesh, false, true, false);
+       Mod_BuildTriangleNeighbors(loadmodel->brush.shadowmesh->neighbor3i, loadmodel->brush.shadowmesh->element3i, loadmodel->brush.shadowmesh->numtriangles);
+
+       if (loadmodel->brush.numsubmodels)
+               loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *));
+
+       // LordHavoc: to clear the fog around the original quake submodel code, I
+       // will explain:
+       // first of all, some background info on the submodels:
+       // model 0 is the map model (the world, named maps/e1m1.bsp for example)
+       // model 1 and higher are submodels (doors and the like, named *1, *2, etc)
+       // now the weird for loop itself:
+       // the loop functions in an odd way, on each iteration it sets up the
+       // current 'mod' model (which despite the confusing code IS the model of
+       // the number i), at the end of the loop it duplicates the model to become
+       // the next submodel, and loops back to set up the new submodel.
+
+       // LordHavoc: now the explanation of my sane way (which works identically):
+       // set up the world model, then on each submodel copy from the world model
+       // and set up the submodel with the respective model info.
+       totalstylesurfaces = 0;
+       totalstyles = 0;
+       for (i = 0;i < mod->brush.numsubmodels;i++)
+       {
+               memset(stylecounts, 0, sizeof(stylecounts));
+               for (k = 0;k < mod->brushq1.submodels[i].numfaces;k++)
+               {
+                       surface = mod->data_surfaces + mod->brushq1.submodels[i].firstface + k;
+                       for (j = 0;j < MAXLIGHTMAPS;j++)
+                               stylecounts[surface->lightmapinfo->styles[j]]++;
+               }
+               for (k = 0;k < 255;k++)
+               {
+                       totalstyles++;
+                       if (stylecounts[k])
+                               totalstylesurfaces += stylecounts[k];
+               }
+       }
+       datapointer = (unsigned char *)Mem_Alloc(mod->mempool, mod->num_surfaces * sizeof(int) + totalstyles * sizeof(model_brush_lightstyleinfo_t) + totalstylesurfaces * sizeof(int *));
        for (i = 0;i < mod->brush.numsubmodels;i++)
        {
+               // LordHavoc: this code was originally at the end of this loop, but
+               // has been transformed to something more readable at the start here.
+
+               if (i > 0)
+               {
+                       char name[10];
+                       // duplicate the basic information
+                       dpsnprintf(name, sizeof(name), "*%i", i);
+                       mod = Mod_FindName(name, loadmodel->name);
+                       // copy the base model to this one
+                       *mod = *loadmodel;
+                       // rename the clone back to its proper name
+                       strlcpy(mod->name, name, sizeof(mod->name));
+                       mod->brush.parentmodel = loadmodel;
+                       // textures and memory belong to the main model
+                       mod->texturepool = NULL;
+                       mod->mempool = NULL;
+                       mod->brush.TraceLineOfSight = NULL;
+                       mod->brush.GetPVS = NULL;
+                       mod->brush.FatPVS = NULL;
+                       mod->brush.BoxTouchingPVS = NULL;
+                       mod->brush.BoxTouchingLeafPVS = NULL;
+                       mod->brush.BoxTouchingVisibleLeafs = NULL;
+                       mod->brush.FindBoxClusters = NULL;
+                       mod->brush.LightPoint = NULL;
+                       mod->brush.AmbientSoundLevelsForPoint = NULL;
+               }
+
+               mod->brush.submodel = i;
+
+               if (loadmodel->brush.submodels)
+                       loadmodel->brush.submodels[i] = mod;
+
                bm = &mod->brushq1.submodels[i];
 
                mod->brushq1.hulls[0].firstclipnode = bm->headnode[0];
@@ -2746,89 +3631,105 @@ void Mod_Q1BSP_Load(model_t *mod, void *buffer)
                        mod->brushq1.hulls[j].lastclipnode = mod->brushq1.numclipnodes - 1;
                }
 
-               mod->brushq1.firstmodelsurface = bm->firstface;
-               mod->brushq1.nummodelsurfaces = bm->numfaces;
+               mod->firstmodelsurface = bm->firstface;
+               mod->nummodelsurfaces = bm->numfaces;
+
+               // make the model surface list (used by shadowing/lighting)
+               mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int);
+               Mod_MakeSortedSurfaces(mod);
+
+               // copy the submodel bounds, then enlarge the yaw and rotated bounds according to radius
+               // (previously this code measured the radius of the vertices of surfaces in the submodel, but that broke submodels that contain only CLIP brushes, which do not produce surfaces)
+               VectorCopy(bm->mins, mod->normalmins);
+               VectorCopy(bm->maxs, mod->normalmaxs);
+               dist = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0]));
+               modelyawradius = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1]));
+               modelyawradius = dist*dist+modelyawradius*modelyawradius;
+               modelradius = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2]));
+               modelradius = modelyawradius + modelradius * modelradius;
+               modelyawradius = sqrt(modelyawradius);
+               modelradius = sqrt(modelradius);
+               mod->yawmins[0] = mod->yawmins[1] = -modelyawradius;
+               mod->yawmins[2] = mod->normalmins[2];
+               mod->yawmaxs[0] = mod->yawmaxs[1] =  modelyawradius;
+               mod->yawmaxs[2] = mod->normalmaxs[2];
+               mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius;
+               mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] =  modelradius;
+               mod->radius = modelradius;
+               mod->radius2 = modelradius * modelradius;
 
-               // this gets altered below if sky is used
+               // this gets altered below if sky or water is used
                mod->DrawSky = NULL;
-               mod->Draw = R_Model_Brush_Draw;
-               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 **));
-               mod->brushq1.pvstexturechainsbuffer = Mem_Alloc(originalloadmodel->mempool,(mod->brushq1.nummodelsurfaces + mod->brushq1.numtextures) * sizeof(msurface_t *));
-               mod->brushq1.pvstexturechainslength = Mem_Alloc(originalloadmodel->mempool, mod->brushq1.numtextures * sizeof(int));
-               Mod_Q1BSP_BuildPVSTextureChains(mod);
-               Mod_Q1BSP_BuildLightmapUpdateChains(originalloadmodel->mempool, mod);
-               if (mod->brushq1.nummodelsurfaces)
-               {
-                       // LordHavoc: calculate bmodel bounding box rather than trusting what it says
-                       mod->normalmins[0] = mod->normalmins[1] = mod->normalmins[2] = 1000000000.0f;
-                       mod->normalmaxs[0] = mod->normalmaxs[1] = mod->normalmaxs[2] = -1000000000.0f;
-                       modelyawradius = 0;
-                       modelradius = 0;
-                       for (j = 0, surf = &mod->brushq1.surfaces[mod->brushq1.firstmodelsurface];j < mod->brushq1.nummodelsurfaces;j++, surf++)
+               mod->DrawAddWaterPlanes = NULL;
+
+               // scan surfaces for sky and water and flag the submodel as possessing these features or not
+               // build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker
+               if (mod->nummodelsurfaces)
+               {
+                       for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++)
+                               if (surface->texture->basematerialflags & MATERIALFLAG_SKY)
+                                       break;
+                       if (j < mod->nummodelsurfaces)
+                               mod->DrawSky = R_Q1BSP_DrawSky;
+
+                       for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++)
+                               if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION))
+                                       break;
+                       if (j < mod->nummodelsurfaces)
+                               mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
+
+                       // build lightstyle update chains
+                       // (used to rapidly mark lightmapupdateflags on many surfaces
+                       // when d_lightstylevalue changes)
+                       memset(stylecounts, 0, sizeof(stylecounts));
+                       for (k = 0;k < mod->nummodelsurfaces;k++)
+                       {
+                               surface = mod->data_surfaces + mod->firstmodelsurface + k;
+                               for (j = 0;j < MAXLIGHTMAPS;j++)
+                                       stylecounts[surface->lightmapinfo->styles[j]]++;
+                       }
+                       mod->brushq1.num_lightstyles = 0;
+                       for (k = 0;k < 255;k++)
+                       {
+                               if (stylecounts[k])
+                               {
+                                       styleinfo[mod->brushq1.num_lightstyles].style = k;
+                                       styleinfo[mod->brushq1.num_lightstyles].value = 0;
+                                       styleinfo[mod->brushq1.num_lightstyles].numsurfaces = 0;
+                                       styleinfo[mod->brushq1.num_lightstyles].surfacelist = (int *)datapointer;datapointer += stylecounts[k] * sizeof(int);
+                                       remapstyles[k] = mod->brushq1.num_lightstyles;
+                                       mod->brushq1.num_lightstyles++;
+                               }
+                       }
+                       for (k = 0;k < mod->nummodelsurfaces;k++)
                        {
-                               // we only need to have a drawsky function if it is used(usually only on world model)
-                               if (surf->texinfo->texture->shader == &Cshader_sky)
-                                       mod->DrawSky = R_Model_Brush_DrawSky;
-                               // LordHavoc: submodels always clip, even if water
-                               if (mod->brush.numsubmodels - 1)
-                                       surf->flags |= SURF_SOLIDCLIP;
-                               // calculate bounding shapes
-                               for (k = 0, vec = surf->mesh.data_vertex3f;k < surf->mesh.num_vertices;k++, vec += 3)
+                               surface = mod->data_surfaces + mod->firstmodelsurface + k;
+                               for (j = 0;j < MAXLIGHTMAPS;j++)
                                {
-                                       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 (surface->lightmapinfo->styles[j] != 255)
+                                       {
+                                               int r = remapstyles[surface->lightmapinfo->styles[j]];
+                                               styleinfo[r].surfacelist[styleinfo[r].numsurfaces++] = mod->firstmodelsurface + k;
+                                       }
                                }
                        }
-                       modelyawradius = sqrt(modelyawradius);
-                       modelradius = sqrt(modelradius);
-                       mod->yawmins[0] = mod->yawmins[1] = - (mod->yawmaxs[0] = mod->yawmaxs[1] = modelyawradius);
-                       mod->yawmins[2] = mod->normalmins[2];
-                       mod->yawmaxs[2] = mod->normalmaxs[2];
-                       mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius;
-                       mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius;
-                       mod->radius = modelradius;
-                       mod->radius2 = modelradius * modelradius;
+                       mod->brushq1.data_lightstyleinfo = (model_brush_lightstyleinfo_t *)datapointer;datapointer += mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t);
+                       memcpy(mod->brushq1.data_lightstyleinfo, styleinfo, mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t));
                }
                else
                {
                        // LordHavoc: empty submodel(lacrima.bsp has such a glitch)
-                       Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadname);
-               }
-               Mod_Q1BSP_BuildSurfaceNeighbors(mod->brushq1.surfaces + mod->brushq1.firstmodelsurface, mod->brushq1.nummodelsurfaces, originalloadmodel->mempool);
-
-               mod->brushq1.visleafs = bm->visleafs;
-
-               // LordHavoc: only register submodels if it is the world
-               // (prevents bsp models from replacing world submodels)
-               if (loadmodel->isworldmodel && i < (mod->brush.numsubmodels - 1))
-               {
-                       char    name[10];
-                       // duplicate the basic information
-                       sprintf(name, "*%i", i+1);
-                       loadmodel = Mod_FindName(name);
-                       *loadmodel = *mod;
-                       strcpy(loadmodel->name, name);
-                       // textures and memory belong to the main model
-                       loadmodel->texturepool = NULL;
-                       loadmodel->mempool = NULL;
-                       mod = loadmodel;
+                       Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadmodel->name);
                }
+               //mod->brushq1.num_visleafs = bm->visleafs;
        }
 
-       loadmodel = originalloadmodel;
+       Mod_Q1BSP_LoadMapBrushes();
+
        //Mod_Q1BSP_ProcessLightList();
+
+       if (developer.integer >= 10)
+               Con_Printf("Some stats for q1bsp model \"%s\": %i faces, %i nodes, %i leafs, %i visleafs, %i visleafportals\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals);
 }
 
 static void Mod_Q2BSP_LoadEntities(lump_t *l)
@@ -3209,12 +4110,14 @@ static void Mod_Q2BSP_LoadModels(lump_t *l)
 */
 }
 
-void static Mod_Q2BSP_Load(model_t *mod, void *buffer)
+void static Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 {
        int i;
        q2dheader_t *header;
 
-       Host_Error("Mod_Q2BSP_Load: not yet implemented\n");
+       Host_Error("Mod_Q2BSP_Load: not yet implemented");
+
+       mod->modeldatatypestring = "Q2BSP";
 
        mod->type = mod_brushq2;
 
@@ -3223,22 +4126,24 @@ void static Mod_Q2BSP_Load(model_t *mod, void *buffer)
        i = LittleLong(header->version);
        if (i != Q2BSPVERSION)
                Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION);
-       mod->brush.ishlbsp = false;
-       if (loadmodel->isworldmodel)
-       {
-               Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
-               // until we get a texture for it...
-               R_ResetQuakeSky();
-       }
 
-       mod_base = (qbyte *)header;
+       mod_base = (unsigned char *)header;
 
        // swap all the lumps
        for (i = 0;i < (int) sizeof(*header) / 4;i++)
                ((int *)header)[i] = LittleLong(((int *)header)[i]);
 
-       // store which lightmap format to use
-       mod->brushq1.lightmaprgba = r_lightmaprgba.integer;
+       mod->brush.qw_md4sum = 0;
+       mod->brush.qw_md4sum2 = 0;
+       for (i = 0;i < Q2HEADER_LUMPS;i++)
+       {
+               if (i == Q2LUMP_ENTITIES)
+                       continue;
+               mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen);
+               if (i == Q2LUMP_VISIBILITY || i == Q2LUMP_LEAFS || i == Q2LUMP_NODES)
+                       continue;
+               mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen);
+       }
 
        Mod_Q2BSP_LoadEntities(&header->lumps[Q2LUMP_ENTITIES]);
        Mod_Q2BSP_LoadPlanes(&header->lumps[Q2LUMP_PLANES]);
@@ -3261,42 +4166,46 @@ void static 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 int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents);
+static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents);
 
 static void Mod_Q3BSP_LoadEntities(lump_t *l)
 {
        const char *data;
-       char key[128], value[4096];
+       char key[128], value[MAX_INPUTLINE];
        float v[3];
        loadmodel->brushq3.num_lightgrid_cellsize[0] = 64;
        loadmodel->brushq3.num_lightgrid_cellsize[1] = 64;
        loadmodel->brushq3.num_lightgrid_cellsize[2] = 128;
        if (!l->filelen)
                return;
-       loadmodel->brush.entities = Mem_Alloc(loadmodel->mempool, l->filelen);
+       loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1);
        memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen);
+       loadmodel->brush.entities[l->filelen] = 0;
        data = loadmodel->brush.entities;
        // some Q3 maps override the lightgrid_cellsize with a worldspawn key
-       if (data && COM_ParseToken(&data, false) && com_token[0] == '{')
+       if (data && COM_ParseToken_Simple(&data, false, false) && com_token[0] == '{')
        {
                while (1)
                {
-                       if (!COM_ParseToken(&data, false))
+                       if (!COM_ParseToken_Simple(&data, false, false))
                                break; // error
                        if (com_token[0] == '}')
                                break; // end of worldspawn
                        if (com_token[0] == '_')
-                               strcpy(key, com_token + 1);
+                               strlcpy(key, com_token + 1, sizeof(key));
                        else
-                               strcpy(key, com_token);
+                               strlcpy(key, com_token, sizeof(key));
                        while (key[strlen(key)-1] == ' ') // remove trailing spaces
                                key[strlen(key)-1] = 0;
-                       if (!COM_ParseToken(&data, false))
+                       if (!COM_ParseToken_Simple(&data, false, false))
                                break; // error
-                       strcpy(value, com_token);
+                       strlcpy(value, com_token, sizeof(value));
                        if (!strcmp("gridsize", key))
                        {
+#if _MSC_VER >= 1400
+#define sscanf sscanf_s
+#endif
                                if (sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) == 3 && v[0] != 0 && v[1] != 0 && v[2] != 0)
                                        VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize);
                        }
@@ -3307,33 +4216,31 @@ static void Mod_Q3BSP_LoadEntities(lump_t *l)
 static void Mod_Q3BSP_LoadTextures(lump_t *l)
 {
        q3dtexture_t *in;
-       q3mtexture_t *out;
+       texture_t *out;
        int i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dtexture_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadTextures: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (texture_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_textures = out;
-       loadmodel->brushq3.num_textures = count;
+       loadmodel->data_textures = out;
+       loadmodel->num_textures = count;
+       loadmodel->num_texturesperskin = loadmodel->num_textures;
 
-       for (i = 0;i < count;i++, in++, out++)
+       for (i = 0;i < count;i++)
        {
-               strlcpy (out->name, in->name, sizeof (out->name));
-               out->surfaceflags = LittleLong(in->surfaceflags);
-               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);
+               strlcpy (out[i].name, in[i].name, sizeof (out[i].name));
+               out[i].surfaceflags = LittleLong(in[i].surfaceflags);
+               out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents));
        }
+
+       if (cls.state == ca_dedicated)
+               return;
+
+       for (i = 0;i < count;i++, in++, out++)
+               Mod_LoadTextureFromQ3Shader(out, out->name, true, true, TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS);
 }
 
 static void Mod_Q3BSP_LoadPlanes(lump_t *l)
@@ -3342,21 +4249,21 @@ static void Mod_Q3BSP_LoadPlanes(lump_t *l)
        mplane_t *out;
        int i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dplane_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadPlanes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (mplane_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_planes = out;
-       loadmodel->brushq3.num_planes = count;
+       loadmodel->brush.data_planes = out;
+       loadmodel->brush.num_planes = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
-               out->normal[0] = LittleLong(in->normal[0]);
-               out->normal[1] = LittleLong(in->normal[1]);
-               out->normal[2] = LittleLong(in->normal[2]);
-               out->dist = LittleLong(in->dist);
+               out->normal[0] = LittleFloat(in->normal[0]);
+               out->normal[1] = LittleFloat(in->normal[1]);
+               out->normal[2] = LittleFloat(in->normal[2]);
+               out->dist = LittleFloat(in->dist);
                PlaneClassify(out);
        }
 }
@@ -3367,25 +4274,53 @@ static void Mod_Q3BSP_LoadBrushSides(lump_t *l)
        q3mbrushside_t *out;
        int i, n, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dbrushside_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+
+       loadmodel->brush.data_brushsides = out;
+       loadmodel->brush.num_brushsides = count;
+
+       for (i = 0;i < count;i++, in++, out++)
+       {
+               n = LittleLong(in->planeindex);
+               if (n < 0 || n >= loadmodel->brush.num_planes)
+                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes);
+               out->plane = loadmodel->brush.data_planes + n;
+               n = LittleLong(in->textureindex);
+               if (n < 0 || n >= loadmodel->num_textures)
+                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures);
+               out->texture = loadmodel->data_textures + n;
+       }
+}
+
+static void Mod_Q3BSP_LoadBrushSides_IG(lump_t *l)
+{
+       q3dbrushside_ig_t *in;
+       q3mbrushside_t *out;
+       int i, n, count;
+
+       in = (q3dbrushside_ig_t *)(mod_base + l->fileofs);
+       if (l->filelen % sizeof(*in))
+               Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name);
+       count = l->filelen / sizeof(*in);
+       out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_brushsides = out;
-       loadmodel->brushq3.num_brushsides = count;
+       loadmodel->brush.data_brushsides = out;
+       loadmodel->brush.num_brushsides = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
                n = LittleLong(in->planeindex);
-               if (n < 0 || n >= loadmodel->brushq3.num_planes)
-                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)\n", n, loadmodel->brushq3.num_planes);
-               out->plane = loadmodel->brushq3.data_planes + n;
+               if (n < 0 || n >= loadmodel->brush.num_planes)
+                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes);
+               out->plane = loadmodel->brush.data_planes + n;
                n = LittleLong(in->textureindex);
-               if (n < 0 || n >= loadmodel->brushq3.num_textures)
-                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)\n", n, loadmodel->brushq3.num_textures);
-               out->texture = loadmodel->brushq3.data_textures + n;
+               if (n < 0 || n >= loadmodel->num_textures)
+                       Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures);
+               out->texture = loadmodel->data_textures + n;
        }
 }
 
@@ -3394,20 +4329,16 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
        q3dbrush_t *in;
        q3mbrush_t *out;
        int i, j, n, c, count, maxplanes;
-       mplane_t *planes;
-       winding_t *temp1, *temp2;
+       colplanef_t *planes;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dbrush_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadBrushes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
-
-       loadmodel->brushq3.data_brushes = out;
-       loadmodel->brushq3.num_brushes = count;
+       out = (q3mbrush_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       temp1 = Winding_New(64);
-       temp2 = Winding_New(64);
+       loadmodel->brush.data_brushes = out;
+       loadmodel->brush.num_brushes = count;
 
        maxplanes = 0;
        planes = NULL;
@@ -3416,14 +4347,14 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
        {
                n = LittleLong(in->firstbrushside);
                c = LittleLong(in->numbrushsides);
-               if (n < 0 || n + c > loadmodel->brushq3.num_brushsides)
-                       Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)\n", n, n + c, loadmodel->brushq3.num_brushsides);
-               out->firstbrushside = loadmodel->brushq3.data_brushsides + n;
+               if (n < 0 || n + c > loadmodel->brush.num_brushsides)
+                       Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)", n, n + c, loadmodel->brush.num_brushsides);
+               out->firstbrushside = loadmodel->brush.data_brushsides + n;
                out->numbrushsides = c;
                n = LittleLong(in->textureindex);
-               if (n < 0 || n >= loadmodel->brushq3.num_textures)
-                       Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)\n", n, loadmodel->brushq3.num_textures);
-               out->texture = loadmodel->brushq3.data_textures + n;
+               if (n < 0 || n >= loadmodel->num_textures)
+                       Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)", n, loadmodel->num_textures);
+               out->texture = loadmodel->data_textures + n;
 
                // make a list of mplane_t structs to construct a colbrush from
                if (maxplanes < out->numbrushsides)
@@ -3431,33 +4362,36 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
                        maxplanes = out->numbrushsides;
                        if (planes)
                                Mem_Free(planes);
-                       planes = Mem_Alloc(tempmempool, sizeof(mplane_t) * maxplanes);
+                       planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes);
                }
                for (j = 0;j < out->numbrushsides;j++)
                {
                        VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal);
                        planes[j].dist = out->firstbrushside[j].plane->dist;
+                       planes[j].q3surfaceflags = out->firstbrushside[j].texture->surfaceflags;
+                       planes[j].texture = out->firstbrushside[j].texture;
                }
                // make the colbrush from the planes
-               out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, temp1, temp2);
+               out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents);
+
+               // this whole loop can take a while (e.g. on redstarrepublic4)
+               CL_KeepaliveMessage(false);
        }
        if (planes)
                Mem_Free(planes);
-       Winding_Free(temp1);
-       Winding_Free(temp2);
 }
 
 static void Mod_Q3BSP_LoadEffects(lump_t *l)
 {
        q3deffect_t *in;
-       q3meffect_t *out;
+       q3deffect_t *out;
        int i, n, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3deffect_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadEffects: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (q3deffect_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
        loadmodel->brushq3.data_effects = out;
        loadmodel->brushq3.num_effects = count;
@@ -3466,9 +4400,12 @@ static void Mod_Q3BSP_LoadEffects(lump_t *l)
        {
                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);
-               out->brush = loadmodel->brushq3.data_brushes + n;
+               if (n >= loadmodel->brush.num_brushes)
+               {
+                       Con_Printf("Mod_Q3BSP_LoadEffects: invalid brushindex %i (%i brushes), setting to -1\n", n, loadmodel->brush.num_brushes);
+                       n = -1;
+               }
+               out->brushindex = n;
                out->unknown = LittleLong(in->unknown);
        }
 }
@@ -3478,37 +4415,29 @@ static void Mod_Q3BSP_LoadVertices(lump_t *l)
        q3dvertex_t *in;
        int i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dvertex_t *)(mod_base + l->fileofs);
        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 + 2 + 2 + 3 + 3 + 3 + 4)));
-       loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_vertex3f + count * 3;
+       loadmodel->brushq3.data_vertex3f = (float *)Mem_Alloc(loadmodel->mempool, count * (sizeof(float) * (3 + 3 + 2 + 2 + 4)));
+       loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_vertex3f + count * 3;
+       loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_normal3f + 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;
+       loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_texcoordlightmap2f + count * 2;
 
        for (i = 0;i < count;i++, in++)
        {
                loadmodel->brushq3.data_vertex3f[i * 3 + 0] = LittleFloat(in->origin3f[0]);
                loadmodel->brushq3.data_vertex3f[i * 3 + 1] = LittleFloat(in->origin3f[1]);
                loadmodel->brushq3.data_vertex3f[i * 3 + 2] = LittleFloat(in->origin3f[2]);
+               loadmodel->brushq3.data_normal3f[i * 3 + 0] = LittleFloat(in->normal3f[0]);
+               loadmodel->brushq3.data_normal3f[i * 3 + 1] = LittleFloat(in->normal3f[1]);
+               loadmodel->brushq3.data_normal3f[i * 3 + 2] = LittleFloat(in->normal3f[2]);
                loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 0] = LittleFloat(in->texcoord2f[0]);
                loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 1] = LittleFloat(in->texcoord2f[1]);
                loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 0] = LittleFloat(in->lightmap2f[0]);
                loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 1] = LittleFloat(in->lightmap2f[1]);
                // svector/tvector are calculated later in face loading
-               loadmodel->brushq3.data_svector3f[i * 3 + 0] = 0;
-               loadmodel->brushq3.data_svector3f[i * 3 + 1] = 0;
-               loadmodel->brushq3.data_svector3f[i * 3 + 2] = 0;
-               loadmodel->brushq3.data_tvector3f[i * 3 + 0] = 0;
-               loadmodel->brushq3.data_tvector3f[i * 3 + 1] = 0;
-               loadmodel->brushq3.data_tvector3f[i * 3 + 2] = 0;
-               loadmodel->brushq3.data_normal3f[i * 3 + 0] = LittleFloat(in->normal3f[0]);
-               loadmodel->brushq3.data_normal3f[i * 3 + 1] = LittleFloat(in->normal3f[1]);
-               loadmodel->brushq3.data_normal3f[i * 3 + 2] = LittleFloat(in->normal3f[2]);
                loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f);
                loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f);
                loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f);
@@ -3522,15 +4451,14 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
        int *out;
        int i, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (int *)(mod_base + l->fileofs);
        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) * 2);
+       out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
        loadmodel->brushq3.num_triangles = count / 3;
        loadmodel->brushq3.data_element3i = out;
-       loadmodel->brushq3.data_neighbor3i = out + count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
@@ -3543,221 +4471,594 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
        }
 }
 
-static void Mod_Q3BSP_LoadLightmaps(lump_t *l)
+static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
 {
-       q3dlightmap_t *in;
-       rtexture_t **out;
-       int i, count;
+       q3dlightmap_t *input_pointer;
+       int i, j, k, count, power, power2, mask, endlightmap, mergewidth, mergeheight;
+       unsigned char *c;
+
+       unsigned char *convertedpixels;
+       char mapname[MAX_QPATH];
+       int size, bytesperpixel, rgbmap[3];
+       qboolean external;
+       unsigned char *inpixels[10000]; // max count q3map2 can output (it uses 4 digits)
+
+       // defaults for q3bsp
+       size = 128;
+       bytesperpixel = 3;
+       rgbmap[0] = 2;
+       rgbmap[1] = 1;
+       rgbmap[2] = 0;
+       external = false;
+       loadmodel->brushq3.lightmapsize = 128;
+
+       if (cls.state == ca_dedicated)
+               return;
 
-       if (!l->filelen)
+       if(mod_q3bsp_nolightmaps.integer)
+       {
                return;
-       in = (void *)(mod_base + l->fileofs);
-       if (l->filelen % sizeof(*in))
-               Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name);
-       count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       }
+       else if(l->filelen)
+       {
+               // prefer internal LMs for compatibility (a BSP contains no info on whether external LMs exist)
+               if (developer_loading.integer)
+                       Con_Printf("Using internal lightmaps\n");
+               input_pointer = (q3dlightmap_t *)(mod_base + l->fileofs);
+               if (l->filelen % sizeof(*input_pointer))
+                       Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name);
+               count = l->filelen / sizeof(*input_pointer);
+               for(i = 0; i < count; ++i)
+                       inpixels[i] = input_pointer[i].rgb;
+       }
+       else
+       {
+               // no internal lightmaps
+               // try external lightmaps
+               if (developer_loading.integer)
+                       Con_Printf("Using external lightmaps\n");
+               FS_StripExtension(loadmodel->name, mapname, sizeof(mapname));
+               inpixels[0] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, 0), false, false);
+               if(!inpixels[0])
+                       return;
 
-       loadmodel->brushq3.data_lightmaps = out;
-       loadmodel->brushq3.num_lightmaps = count;
+               // using EXTERNAL lightmaps instead
+               if(image_width != (int) CeilPowerOf2(image_width) || image_width != image_height)
+               {
+                       Mem_Free(inpixels[0]);
+                       Host_Error("Mod_Q3BSP_LoadLightmaps: invalid external lightmap size in %s",loadmodel->name);
+               }
 
-       for (i = 0;i < count;i++, in++, out++)
-               *out = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", i), 128, 128, in->rgb, TEXTYPE_RGB, TEXF_FORCELINEAR | TEXF_PRECACHE, NULL);
+               size = image_width;
+               bytesperpixel = 4;
+               rgbmap[0] = 0;
+               rgbmap[1] = 1;
+               rgbmap[2] = 2;
+               external = true;
+
+               for(count = 1; ; ++count)
+               {
+                       inpixels[count] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, count), false, false);
+                       if(!inpixels[count])
+                               break; // we got all of them
+                       if(image_width != size || image_height != size)
+                       {
+                               for(i = 0; i <= count; ++i)
+                                       Mem_Free(inpixels[i]);
+                               Host_Error("Mod_Q3BSP_LoadLightmaps: invalid external lightmap size in %s",loadmodel->name);
+                       }
+               }
+       }
+
+       convertedpixels = (unsigned char *) Mem_Alloc(tempmempool, size*size*4);
+       loadmodel->brushq3.lightmapsize = size;
+       loadmodel->brushq3.num_originallightmaps = count;
+
+       // now check the surfaces to see if any of them index an odd numbered
+       // lightmap, if so this is not a deluxemapped bsp file
+       //
+       // also check what lightmaps are actually used, because q3map2 sometimes
+       // (always?) makes an unused one at the end, which
+       // q3map2 sometimes (or always?) makes a second blank lightmap for no
+       // reason when only one lightmap is used, which can throw off the
+       // deluxemapping detection method, so check 2-lightmap bsp's specifically
+       // to see if the second lightmap is blank, if so it is not deluxemapped.
+       loadmodel->brushq3.deluxemapping = !(count & 1);
+       loadmodel->brushq3.deluxemapping_modelspace = true;
+       endlightmap = 0;
+       if (loadmodel->brushq3.deluxemapping)
+       {
+               int facecount = faceslump->filelen / sizeof(q3dface_t);
+               q3dface_t *faces = (q3dface_t *)(mod_base + faceslump->fileofs);
+               for (i = 0;i < facecount;i++)
+               {
+                       j = LittleLong(faces[i].lightmapindex);
+                       if (j >= 0)
+                       {
+                               endlightmap = max(endlightmap, j + 1);
+                               if ((j & 1) || j + 1 >= count)
+                               {
+                                       loadmodel->brushq3.deluxemapping = false;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       // q3map2 sometimes (or always?) makes a second blank lightmap for no
+       // reason when only one lightmap is used, which can throw off the
+       // deluxemapping detection method, so check 2-lightmap bsp's specifically
+       // to see if the second lightmap is blank, if so it is not deluxemapped.
+       //
+       // further research has shown q3map2 sometimes creates a deluxemap and two
+       // blank lightmaps, which must be handled properly as well
+       if (endlightmap == 1 && count > 1)
+       {
+               c = inpixels[1];
+               for (i = 0;i < size*size;i++)
+               {
+                       if (c[bytesperpixel*i + rgbmap[0]])
+                               break;
+                       if (c[bytesperpixel*i + rgbmap[1]])
+                               break;
+                       if (c[bytesperpixel*i + rgbmap[2]])
+                               break;
+               }
+               if (i == size*size)
+               {
+                       // all pixels in the unused lightmap were black...
+                       loadmodel->brushq3.deluxemapping = false;
+               }
+       }
+
+       Con_DPrintf("%s is %sdeluxemapped\n", loadmodel->name, loadmodel->brushq3.deluxemapping ? "" : "not ");
+
+       // figure out what the most reasonable merge power is within limits
+
+       loadmodel->brushq3.num_lightmapmergepower = 0;
+
+       for(i = 0; (128 << i) < size; ++i)
+               ;
+       // i is now 0 for 128, 1 for 256, etc
+
+       for (power = 1;power + i <= mod_q3bsp_lightmapmergepower.integer && (size << power) <= gl_max_texture_size && (1 << (power * 2)) < 4 * (count >> loadmodel->brushq3.deluxemapping); power++)
+               loadmodel->brushq3.num_lightmapmergepower = power;
+
+       loadmodel->brushq3.num_lightmapmerge = 1 << loadmodel->brushq3.num_lightmapmergepower;
+
+       loadmodel->brushq3.num_mergedlightmaps = ((count >> loadmodel->brushq3.deluxemapping) + (1 << (loadmodel->brushq3.num_lightmapmergepower * 2)) - 1) >> (loadmodel->brushq3.num_lightmapmergepower * 2);
+       loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *));
+       if (loadmodel->brushq3.deluxemapping)
+               loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *));
+
+       // allocate a texture pool if we need it
+       if (loadmodel->texturepool == NULL && cls.state != ca_dedicated)
+               loadmodel->texturepool = R_AllocTexturePool();
+
+       power = loadmodel->brushq3.num_lightmapmergepower;
+       power2 = power * 2;
+       mask = (1 << power) - 1;
+       for (i = 0;i < count;i++)
+       {
+               // figure out which merged lightmap texture this fits into
+               int lightmapindex = i >> (loadmodel->brushq3.deluxemapping + power2);
+               for (k = 0;k < size*size;k++)
+               {
+                       convertedpixels[k*4+0] = inpixels[i][k*bytesperpixel+rgbmap[0]];
+                       convertedpixels[k*4+1] = inpixels[i][k*bytesperpixel+rgbmap[1]];
+                       convertedpixels[k*4+2] = inpixels[i][k*bytesperpixel+rgbmap[2]];
+                       convertedpixels[k*4+3] = 255;
+               }
+               if (loadmodel->brushq3.num_lightmapmergepower > 0)
+               {
+                       // if the lightmap has not been allocated yet, create it
+                       if (!loadmodel->brushq3.data_lightmaps[lightmapindex])
+                       {
+                               // create a lightmap only as large as necessary to hold the
+                               // remaining size*size blocks
+                               // if there are multiple merged lightmap textures then they will
+                               // all be full size except the last one which may be smaller
+                               // because it only needs to the remaining blocks, and it will often
+                               // be odd sizes like 2048x512 due to only being 25% full or so.
+                               j = (count >> loadmodel->brushq3.deluxemapping) - (lightmapindex << power2);
+                               for (mergewidth = 1;mergewidth < j && mergewidth < (1 << power);mergewidth *= 2)
+                                       ;
+                               for (mergeheight = 1;mergewidth*mergeheight < j && mergeheight < (1 << power);mergeheight *= 2)
+                                       ;
+                               if (developer_loading.integer)
+                                       Con_Printf("lightmap merge texture #%i is %ix%i (%i of %i used)\n", lightmapindex, mergewidth*size, mergeheight*size, min(j, mergewidth*mergeheight), mergewidth*mergeheight);
+                               loadmodel->brushq3.data_lightmaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), mergewidth * size, mergeheight * size, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), NULL);
+                               if (loadmodel->brushq3.data_deluxemaps)
+                                       loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%04i", lightmapindex), mergewidth * size, mergeheight * size, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), NULL);
+                       }
+                       mergewidth = R_TextureWidth(loadmodel->brushq3.data_lightmaps[lightmapindex]) / size;
+                       mergeheight = R_TextureHeight(loadmodel->brushq3.data_lightmaps[lightmapindex]) / size;
+                       j = (i >> loadmodel->brushq3.deluxemapping) & ((1 << power2) - 1);
+                       if (loadmodel->brushq3.deluxemapping && (i & 1))
+                               R_UpdateTexture(loadmodel->brushq3.data_deluxemaps[lightmapindex], convertedpixels, (j % mergewidth) * size, (j / mergewidth) * size, size, size);
+                       else
+                               R_UpdateTexture(loadmodel->brushq3.data_lightmaps [lightmapindex], convertedpixels, (j % mergewidth) * size, (j / mergewidth) * size, size, size);
+               }
+               else
+               {
+                       // figure out which merged lightmap texture this fits into
+                       if (loadmodel->brushq3.deluxemapping && (i & 1))
+                               loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("deluxemap%04i", lightmapindex), size, size, convertedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), NULL);
+                       else
+                               loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va("lightmap%04i", lightmapindex), size, size, convertedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_PRECACHE | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), NULL);
+               }
+       }
+
+       Mem_Free(convertedpixels);
+       if(external)
+       {
+               for(i = 0; i < count; ++i)
+                       Mem_Free(inpixels[i]);
+       }
+}
+
+static void Mod_Q3BSP_BuildBBoxes(const int *element3i, int num_triangles, const float *vertex3f, float **collisionbbox6f, int *collisionstride, int stride)
+{
+       int j, k, cnt, tri;
+       float *mins, *maxs;
+       const float *vert;
+       if(stride > 0)
+       {
+               *collisionstride = stride;
+               cnt = (num_triangles + stride - 1) / stride;
+               *collisionbbox6f = (float *) Mem_Alloc(loadmodel->mempool, sizeof(float[6]) * cnt);
+               for(j = 0; j < cnt; ++j)
+               {
+                       mins = &((*collisionbbox6f)[6 * j + 0]);
+                       maxs = &((*collisionbbox6f)[6 * j + 3]);
+                       for(k = 0; k < stride; ++k)
+                       {
+                               tri = j * stride + k;
+                               if(tri >= num_triangles)
+                                       break;
+                               vert = &(vertex3f[element3i[3 * tri + 0] * 3]);
+                               if(!k || vert[0] < mins[0]) mins[0] = vert[0];
+                               if(!k || vert[1] < mins[1]) mins[1] = vert[1];
+                               if(!k || vert[2] < mins[2]) mins[2] = vert[2];
+                               if(!k || vert[0] > maxs[0]) maxs[0] = vert[0];
+                               if(!k || vert[1] > maxs[1]) maxs[1] = vert[1];
+                               if(!k || vert[2] > maxs[2]) maxs[2] = vert[2];
+                               vert = &(vertex3f[element3i[3 * tri + 1] * 3]);
+                               if(vert[0] < mins[0]) mins[0] = vert[0];
+                               if(vert[1] < mins[1]) mins[1] = vert[1];
+                               if(vert[2] < mins[2]) mins[2] = vert[2];
+                               if(vert[0] > maxs[0]) maxs[0] = vert[0];
+                               if(vert[1] > maxs[1]) maxs[1] = vert[1];
+                               if(vert[2] > maxs[2]) maxs[2] = vert[2];
+                               vert = &(vertex3f[element3i[3 * tri + 2] * 3]);
+                               if(vert[0] < mins[0]) mins[0] = vert[0];
+                               if(vert[1] < mins[1]) mins[1] = vert[1];
+                               if(vert[2] < mins[2]) mins[2] = vert[2];
+                               if(vert[0] > maxs[0]) maxs[0] = vert[0];
+                               if(vert[1] > maxs[1]) maxs[1] = vert[1];
+                               if(vert[2] > maxs[2]) maxs[2] = vert[2];
+                       }
+               }
+       }
+       else
+       {
+               *collisionstride = 0;
+               *collisionbbox6f = NULL;
+       }
 }
 
+typedef struct patchtess_s
+{
+       patchinfo_t info;
+
+       // Auxiliary data used only by patch loading code in Mod_Q3BSP_LoadFaces
+       int surface_id;
+       float lodgroup[6];
+       float *originalvertex3f;
+} patchtess_t;
+
+#define PATCHTESS_SAME_LODGROUP(a,b) \
+       ( \
+               (a).lodgroup[0] == (b).lodgroup[0] && \
+               (a).lodgroup[1] == (b).lodgroup[1] && \
+               (a).lodgroup[2] == (b).lodgroup[2] && \
+               (a).lodgroup[3] == (b).lodgroup[3] && \
+               (a).lodgroup[4] == (b).lodgroup[4] && \
+               (a).lodgroup[5] == (b).lodgroup[5] \
+       )
+
 static void Mod_Q3BSP_LoadFaces(lump_t *l)
 {
-       q3dface_t *in;
-       q3mface_t *out;
-       int i, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xlevel, ylevel, row0, row1, x, y, *e, finalvertices, finaltriangles;
+       q3dface_t *in, *oldin;
+       msurface_t *out, *oldout;
+       int i, oldi, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xtess, ytess, finalvertices, finaltriangles, firstvertex, firstelement, type, oldnumtriangles, oldnumtriangles2, meshvertices, meshtriangles, numvertices, numtriangles, cxtess, cytess;
+       float lightmaptcbase[2], lightmaptcscale[2];
        //int *originalelement3i;
        //int *originalneighbor3i;
        float *originalvertex3f;
        //float *originalsvector3f;
        //float *originaltvector3f;
-       //float *originalnormal3f;
+       float *originalnormal3f;
        float *originalcolor4f;
        float *originaltexcoordtexture2f;
        float *originaltexcoordlightmap2f;
        float *v;
+       patchtess_t *patchtess = NULL;
+       int patchtesscount = 0;
+       qboolean again;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dface_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadFaces: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (msurface_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_faces = out;
-       loadmodel->brushq3.num_faces = count;
+       loadmodel->data_surfaces = out;
+       loadmodel->num_surfaces = count;
 
-       for (i = 0;i < count;i++, in++, out++)
+       if(count > 0)
+               patchtess = (patchtess_t*) Mem_Alloc(tempmempool, count * sizeof(*patchtess));
+
+       i = 0;
+       oldi = i;
+       oldin = in;
+       oldout = out;
+       meshvertices = 0;
+       meshtriangles = 0;
+       for (;i < count;i++, in++, out++)
        {
                // check face type first
-               out->type = LittleLong(in->type);
-               if (out->type != Q3FACETYPE_POLYGON
-                && out->type != Q3FACETYPE_PATCH
-                && out->type != Q3FACETYPE_MESH
-                && out->type != Q3FACETYPE_FLARE)
-               {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i: unknown face type %i\n", i, out->type);
-                       out->num_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0; // error
+               type = LittleLong(in->type);
+               if (type != Q3FACETYPE_FLAT
+                && type != Q3FACETYPE_PATCH
+                && type != Q3FACETYPE_MESH
+                && type != Q3FACETYPE_FLARE)
+               {
+                       Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: unknown face type %i\n", i, type);
                        continue;
                }
 
                n = LittleLong(in->textureindex);
-               if (n < 0 || n >= loadmodel->brushq3.num_textures)
+               if (n < 0 || n >= loadmodel->num_textures)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i: invalid textureindex %i (%i textures)\n", i, n, loadmodel->brushq3.num_textures);
-                       out->num_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0; // error
+                       Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: invalid textureindex %i (%i textures)\n", i, n, loadmodel->num_textures);
                        continue;
-                       n = 0;
                }
-               out->texture = loadmodel->brushq3.data_textures + n;
+               out->texture = loadmodel->data_textures + n;
                n = LittleLong(in->effectindex);
                if (n < -1 || n >= loadmodel->brushq3.num_effects)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid effectindex %i (%i effects)\n", i, out->texture->name, n, loadmodel->brushq3.num_effects);
+                       if (developer.integer >= 100)
+                               Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid effectindex %i (%i effects)\n", i, out->texture->name, n, loadmodel->brushq3.num_effects);
                        n = -1;
                }
                if (n == -1)
                        out->effect = NULL;
                else
                        out->effect = loadmodel->brushq3.data_effects + n;
-               n = LittleLong(in->lightmapindex);
-               if (n < -1 || n >= loadmodel->brushq3.num_lightmaps)
+
+               if (cls.state != ca_dedicated)
                {
-                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, n, loadmodel->brushq3.num_lightmaps);
-                       n = -1;
-               }
-               if (n == -1)
                        out->lightmaptexture = NULL;
-               else
-                       out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n];
+                       out->deluxemaptexture = r_texture_blanknormalmap;
+                       n = LittleLong(in->lightmapindex);
+                       if (n < 0)
+                               n = -1;
+                       else if (n >= loadmodel->brushq3.num_originallightmaps)
+                       {
+                               if(loadmodel->brushq3.num_originallightmaps != 0)
+                                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, n, loadmodel->brushq3.num_originallightmaps);
+                               n = -1;
+                       }
+                       else
+                       {
+                               out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n >> (loadmodel->brushq3.num_lightmapmergepower * 2 + loadmodel->brushq3.deluxemapping)];
+                               if (loadmodel->brushq3.deluxemapping)
+                                       out->deluxemaptexture = loadmodel->brushq3.data_deluxemaps[n >> (loadmodel->brushq3.num_lightmapmergepower * 2 + loadmodel->brushq3.deluxemapping)];
+                       }
+               }
 
-               out->firstvertex = LittleLong(in->firstvertex);
-               out->num_vertices = LittleLong(in->numvertices);
-               out->firstelement = LittleLong(in->firstelement);
-               out->num_triangles = LittleLong(in->numelements) / 3;
-               if (out->num_triangles * 3 != LittleLong(in->numelements))
+               firstvertex = LittleLong(in->firstvertex);
+               numvertices = LittleLong(in->numvertices);
+               firstelement = LittleLong(in->firstelement);
+               numtriangles = LittleLong(in->numelements) / 3;
+               if (numtriangles * 3 != LittleLong(in->numelements))
                {
                        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->num_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0; // error
                        continue;
                }
-               if (out->firstvertex < 0 || out->firstvertex + out->num_vertices > loadmodel->brushq3.num_vertices)
+               if (firstvertex < 0 || firstvertex + numvertices > loadmodel->brushq3.num_vertices)
                {
-                       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->num_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0; // error
+                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, firstvertex, firstvertex + numvertices, loadmodel->brushq3.num_vertices);
                        continue;
                }
-               if (out->firstelement < 0 || out->firstelement + out->num_triangles * 3 > loadmodel->brushq3.num_triangles * 3)
+               if (firstelement < 0 || firstelement + numtriangles * 3 > loadmodel->brushq3.num_triangles * 3)
                {
-                       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->num_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0; // error
+                       Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, firstelement, firstelement + numtriangles * 3, loadmodel->brushq3.num_triangles * 3);
                        continue;
                }
-               switch(out->type)
+               switch(type)
                {
-               case Q3FACETYPE_POLYGON:
+               case Q3FACETYPE_FLAT:
                case Q3FACETYPE_MESH:
                        // no processing necessary
-                       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;
                        break;
                case Q3FACETYPE_PATCH:
                        patchsize[0] = LittleLong(in->specific.patch.patchsize[0]);
                        patchsize[1] = LittleLong(in->specific.patch.patchsize[1]);
-                       if (patchsize[0] < 1 || patchsize[1] < 1)
+                       if (numvertices != (patchsize[0] * patchsize[1]) || patchsize[0] < 3 || patchsize[1] < 3 || !(patchsize[0] & 1) || !(patchsize[1] & 1) || patchsize[0] * patchsize[1] >= min(r_subdivisions_maxvertices.integer, r_subdivisions_collision_maxvertices.integer))
+                       {
+                               Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid patchsize %ix%i\n", i, out->texture->name, patchsize[0], patchsize[1]);
+                               continue;
+                       }
+                       originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3;
+
+                       // convert patch to Q3FACETYPE_MESH
+                       xtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value);
+                       ytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value);
+                       // bound to user settings
+                       xtess = bound(r_subdivisions_mintess.integer, xtess, r_subdivisions_maxtess.integer);
+                       ytess = bound(r_subdivisions_mintess.integer, ytess, r_subdivisions_maxtess.integer);
+                       // bound to sanity settings
+                       xtess = bound(0, xtess, 1024);
+                       ytess = bound(0, ytess, 1024);
+
+                       // lower quality collision patches! Same procedure as before, but different cvars
+                       // convert patch to Q3FACETYPE_MESH
+                       cxtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value);
+                       cytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value);
+                       // bound to user settings
+                       cxtess = bound(r_subdivisions_collision_mintess.integer, cxtess, r_subdivisions_collision_maxtess.integer);
+                       cytess = bound(r_subdivisions_collision_mintess.integer, cytess, r_subdivisions_collision_maxtess.integer);
+                       // bound to sanity settings
+                       cxtess = bound(0, cxtess, 1024);
+                       cytess = bound(0, cytess, 1024);
+
+                       // store it for the LOD grouping step
+                       patchtess[patchtesscount].info.xsize = patchsize[0];
+                       patchtess[patchtesscount].info.ysize = patchsize[1];
+                       patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].xtess = xtess;
+                       patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].ytess = ytess;
+                       patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].xtess = cxtess;
+                       patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].ytess = cytess;
+       
+                       patchtess[patchtesscount].surface_id = i;
+                       patchtess[patchtesscount].lodgroup[0] = in->specific.patch.mins[0];
+                       patchtess[patchtesscount].lodgroup[1] = in->specific.patch.mins[1];
+                       patchtess[patchtesscount].lodgroup[2] = in->specific.patch.mins[2];
+                       patchtess[patchtesscount].lodgroup[3] = in->specific.patch.maxs[0];
+                       patchtess[patchtesscount].lodgroup[4] = in->specific.patch.maxs[1];
+                       patchtess[patchtesscount].lodgroup[5] = in->specific.patch.maxs[2];
+                       patchtess[patchtesscount].originalvertex3f = originalvertex3f;
+                       ++patchtesscount;
+                       break;
+               case Q3FACETYPE_FLARE:
+                       if (developer.integer >= 100)
+                               Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name);
+                       // don't render it
+                       continue;
+               }
+               out->num_vertices = numvertices;
+               out->num_triangles = numtriangles;
+               meshvertices += out->num_vertices;
+               meshtriangles += out->num_triangles;
+       }
+
+       // Fix patches tesselations so that they make no seams
+       do
+       {
+               again = false;
+               for(i = 0; i < patchtesscount; ++i)
+               {
+                       for(j = i+1; j < patchtesscount; ++j)
+                       {
+                               if (!PATCHTESS_SAME_LODGROUP(patchtess[i], patchtess[j]))
+                                       continue;
+
+                               if (Q3PatchAdjustTesselation(3, &patchtess[i].info, patchtess[i].originalvertex3f, &patchtess[j].info, patchtess[j].originalvertex3f) )
+                                       again = true;
+                       }
+               }
+       }
+       while (again);
+
+       // Calculate resulting number of triangles
+       for(i = 0; i < patchtesscount; ++i)
+       {
+               finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_VISUAL].xtess);
+               finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_VISUAL].ytess);
+               numvertices = finalwidth * finalheight;
+               numtriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+
+               oldout[patchtess[i].surface_id].num_vertices = numvertices;
+               oldout[patchtess[i].surface_id].num_triangles = numtriangles;
+               meshvertices += oldout[patchtess[i].surface_id].num_vertices;
+               meshtriangles += oldout[patchtess[i].surface_id].num_triangles;
+       }
+
+       i = oldi;
+       in = oldin;
+       out = oldout;
+       Mod_AllocSurfMesh(loadmodel->mempool, meshvertices, meshtriangles, false, true, false);
+       meshvertices = 0;
+       meshtriangles = 0;
+       for (;i < count && meshvertices + out->num_vertices <= loadmodel->surfmesh.num_vertices;i++, in++, out++)
+       {
+               if (out->num_vertices < 3 || out->num_triangles < 1)
+                       continue;
+
+               type = LittleLong(in->type);
+               firstvertex = LittleLong(in->firstvertex);
+               firstelement = LittleLong(in->firstelement);
+               out->num_firstvertex = meshvertices;
+               out->num_firsttriangle = meshtriangles;
+               switch(type)
+               {
+               case Q3FACETYPE_FLAT:
+               case Q3FACETYPE_MESH:
+                       // no processing necessary, except for lightmap merging
+                       for (j = 0;j < out->num_vertices;j++)
+                       {
+                               (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 0];
+                               (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 1];
+                               (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 2];
+                               (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 0];
+                               (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 1];
+                               (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 2];
+                               (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 0];
+                               (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 1];
+                               (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 0];
+                               (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 1];
+                               (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 0] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 0];
+                               (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 1] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 1];
+                               (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 2] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 2];
+                               (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 3] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 3];
+                       }
+                       for (j = 0;j < out->num_triangles*3;j++)
+                               (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = loadmodel->brushq3.data_element3i[firstelement + j] + out->num_firstvertex;
+                       break;
+               case Q3FACETYPE_PATCH:
+                       patchsize[0] = LittleLong(in->specific.patch.patchsize[0]);
+                       patchsize[1] = LittleLong(in->specific.patch.patchsize[1]);
+                       originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3;
+                       originalnormal3f = loadmodel->brushq3.data_normal3f + firstvertex * 3;
+                       originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + firstvertex * 2;
+                       originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + firstvertex * 2;
+                       originalcolor4f = loadmodel->brushq3.data_color4f + firstvertex * 4;
+
+                       xtess = ytess = cxtess = cytess = -1;
+                       for(j = 0; j < patchtesscount; ++j)
+                               if(patchtess[j].surface_id == i)
+                               {
+                                       xtess = patchtess[j].info.lods[PATCH_LOD_VISUAL].xtess;
+                                       ytess = patchtess[j].info.lods[PATCH_LOD_VISUAL].ytess;
+                                       cxtess = patchtess[j].info.lods[PATCH_LOD_COLLISION].xtess;
+                                       cytess = patchtess[j].info.lods[PATCH_LOD_COLLISION].ytess;
+                                       break;
+                               }
+                       if(xtess == -1)
                        {
-                               Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid patchsize %ix%i\n", i, out->texture->name, patchsize[0], patchsize[1]);
-                               out->num_vertices = 0;
-                               out->num_triangles = 0;
-                               out->type = 0; // error
-                               continue;
+                               Con_Printf("ERROR: patch %d isn't preprocessed?!?\n", i);
+                               xtess = ytess = cxtess = cytess = 0;
                        }
-                       // 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;
+
+                       finalwidth = Q3PatchDimForTess(patchsize[0],xtess); //((patchsize[0] - 1) * xtess) + 1;
+                       finalheight = Q3PatchDimForTess(patchsize[1],ytess); //((patchsize[1] - 1) * ytess) + 1;
                        finalvertices = finalwidth * finalheight;
                        finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
-                       originalvertex3f = loadmodel->brushq3.data_vertex3f + out->firstvertex * 3;
-                       //originalsvector3f = loadmodel->brushq3.data_svector3f + out->firstvertex * 3;
-                       //originaltvector3f = loadmodel->brushq3.data_tvector3f + out->firstvertex * 3;
-                       //originalnormal3f = loadmodel->brushq3.data_normal3f + out->firstvertex * 3;
-                       originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + out->firstvertex * 2;
-                       originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + out->firstvertex * 2;
-                       originalcolor4f = loadmodel->brushq3.data_color4f + out->firstvertex * 4;
-                       //originalelement3i = loadmodel->brushq3.data_element3i + out->firstelement;
-                       //originalneighbor3i = loadmodel->brushq3.data_neighbor3i + out->firstelement;
-                       /*
-                       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);
-                       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 = Mem_Alloc(loadmodel->mempool, sizeof(int[6]) * finaltriangles);
-                       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;
+                       type = Q3FACETYPE_MESH;
                        // 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)
+                       Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, xtess, ytess);
+                       Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalnormal3f, xtess, ytess);
+                       Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordtexture2f, xtess, ytess);
+                       Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordlightmap2f, xtess, ytess);
+                       Q3PatchTesselateFloat(4, sizeof(float[4]), (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[4]), originalcolor4f, xtess, ytess);
+                       Q3PatchTriangleElements((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), finalwidth, finalheight, out->num_firstvertex);
+
+                       out->num_triangles = Mod_RemoveDegenerateTriangles(out->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), loadmodel->surfmesh.data_vertex3f);
+
+                       if (developer.integer >= 100)
                        {
                                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);
@@ -3765,42 +5066,78 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                        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;
+                       // build the lower quality collision geometry
+                       finalwidth = Q3PatchDimForTess(patchsize[0],cxtess); //((patchsize[0] - 1) * cxtess) + 1;
+                       finalheight = Q3PatchDimForTess(patchsize[1],cytess); //((patchsize[1] - 1) * cytess) + 1;
+                       finalvertices = finalwidth * finalheight;
+                       finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+
+                       out->data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices);
+                       out->data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles);
+                       out->num_collisionvertices = finalvertices;
+                       out->num_collisiontriangles = finaltriangles;
+                       Q3PatchTesselateFloat(3, sizeof(float[3]), out->data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess);
+                       Q3PatchTriangleElements(out->data_collisionelement3i, finalwidth, finalheight, 0);
+
+                       //Mod_SnapVertices(3, out->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), 0.25);
+                       Mod_SnapVertices(3, out->num_collisionvertices, out->data_collisionvertex3f, 1);
+
+                       oldnumtriangles = out->num_triangles;
+                       oldnumtriangles2 = out->num_collisiontriangles;
+                       out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(out->num_collisiontriangles, out->data_collisionelement3i, out->data_collisionelement3i, out->data_collisionvertex3f);
+
+                       // now optimize the collision mesh by finding triangle bboxes...
+                       Mod_Q3BSP_BuildBBoxes(out->data_collisionelement3i, out->num_collisiontriangles, out->data_collisionvertex3f, &out->data_collisionbbox6f, &out->num_collisionbboxstride, mod_q3bsp_curves_collisions_stride.integer);
+                       Mod_Q3BSP_BuildBBoxes(loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle, out->num_triangles, loadmodel->surfmesh.data_vertex3f, &out->data_bbox6f, &out->num_bboxstride, mod_q3bsp_curves_stride.integer);
+
+                       if (developer.integer >= 100)
+                               Con_Printf("Mod_Q3BSP_LoadFaces: %ix%i curve became %i:%i vertices / %i:%i triangles (%i:%i degenerate)\n", patchsize[0], patchsize[1], out->num_vertices, out->num_collisionvertices, oldnumtriangles, oldnumtriangles2, oldnumtriangles - out->num_triangles, oldnumtriangles2 - out->num_collisiontriangles);
                        break;
-               case Q3FACETYPE_FLARE:
-                       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_vertices = 0;
-                       out->num_triangles = 0;
-                       out->type = 0;
+               default:
                        break;
                }
+               meshvertices += out->num_vertices;
+               meshtriangles += out->num_triangles;
                for (j = 0, invalidelements = 0;j < out->num_triangles * 3;j++)
-                       if (out->data_element3i[j] < 0 || out->data_element3i[j] >= out->num_vertices)
+                       if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + 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->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);
+                       Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, type, out->texture->name, out->texture->surfaceflags, firstvertex, out->num_vertices, 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->num_vertices)
-                                       out->data_element3i[j] = 0;
+                               Con_Printf(" %i", (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] - out->num_firstvertex);
+                               if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices)
+                                       (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = out->num_firstvertex;
                        }
-                       Con_Printf("\n");
+                       Con_Print("\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)
+                       if (cls.state != ca_dedicated && out->lightmaptexture)
+                       {
+                               // figure out which part of the merged lightmap this fits into
+                               int lightmapindex = LittleLong(in->lightmapindex) >> loadmodel->brushq3.deluxemapping;
+                               int mergewidth = R_TextureWidth(out->lightmaptexture) / loadmodel->brushq3.lightmapsize;
+                               int mergeheight = R_TextureHeight(out->lightmaptexture) / loadmodel->brushq3.lightmapsize;
+                               lightmapindex &= mergewidth * mergeheight - 1;
+                               lightmaptcscale[0] = 1.0f / mergewidth;
+                               lightmaptcscale[1] = 1.0f / mergeheight;
+                               lightmaptcbase[0] = (lightmapindex % mergewidth) * lightmaptcscale[0];
+                               lightmaptcbase[1] = (lightmapindex / mergewidth) * lightmaptcscale[1];
+                               // modify the lightmap texcoords to match this region of the merged lightmap
+                               for (j = 0, v = loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex;j < out->num_vertices;j++, v += 2)
+                               {
+                                       v[0] = v[0] * lightmaptcscale[0] + lightmaptcbase[0];
+                                       v[1] = v[1] * lightmaptcscale[1] + lightmaptcbase[1];
+                               }
+                       }
+                       VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->mins);
+                       VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->maxs);
+                       for (j = 1, v = (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex) + 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]);
@@ -3816,109 +5153,63 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        out->maxs[1] += 1.0f;
                        out->maxs[2] += 1.0f;
                }
+               // set lightmap styles for consistency with q1bsp
+               //out->lightmapinfo->styles[0] = 0;
+               //out->lightmapinfo->styles[1] = 255;
+               //out->lightmapinfo->styles[2] = 255;
+               //out->lightmapinfo->styles[3] = 255;
        }
 
-       // LordHavoc: experimental array merger (disabled because it wastes time and uses 2x memory while merging)
-       /*
-       {
-       int totalverts, totaltris;
-       int originalnum_vertices;
-       float *originaldata_vertex3f;
-       float *originaldata_texcoordtexture2f;
-       float *originaldata_texcoordlightmap2f;
-       float *originaldata_svector3f;
-       float *originaldata_tvector3f;
-       float *originaldata_normal3f;
-       float *originaldata_color4f;
-       int originalnum_triangles;
-       int *originaldata_element3i;
-       int *originaldata_neighbor3i;
-
-       totalverts = 0;
-       totaltris = 0;
-       for (i = 0, out = loadmodel->brushq3.data_faces;i < count;i++, out++)
-       {
-               if (!out->type)
-                       continue;
-               totalverts += out->num_vertices;
-               totaltris += out->num_triangles;
-       }
-
-       originalnum_vertices = loadmodel->brushq3.num_vertices;
-       originaldata_vertex3f = loadmodel->brushq3.data_vertex3f;
-       originaldata_texcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f;
-       originaldata_texcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f;
-       originaldata_svector3f = loadmodel->brushq3.data_svector3f;
-       originaldata_tvector3f = loadmodel->brushq3.data_tvector3f;
-       originaldata_normal3f = loadmodel->brushq3.data_normal3f;
-       originaldata_color4f = loadmodel->brushq3.data_color4f;
-       originalnum_triangles = loadmodel->brushq3.num_triangles;
-       originaldata_element3i = loadmodel->brushq3.data_element3i;
-       originaldata_neighbor3i = loadmodel->brushq3.data_neighbor3i;
-       loadmodel->brushq3.num_vertices = totalverts;
-       loadmodel->brushq3.data_vertex3f = Mem_Alloc(loadmodel->mempool, totalverts * (sizeof(float) * (3 + 2 + 2 + 3 + 3 + 3 + 4)) + totaltris * (sizeof(int) * (3 * 2)));
-       loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_vertex3f + totalverts * 3;
-       loadmodel->brushq3.data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordtexture2f + totalverts * 2;
-       loadmodel->brushq3.data_svector3f = loadmodel->brushq3.data_texcoordlightmap2f + totalverts * 2;
-       loadmodel->brushq3.data_tvector3f = loadmodel->brushq3.data_svector3f + totalverts * 3;
-       loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_tvector3f + totalverts * 3;
-       loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_normal3f + totalverts * 3;
-       loadmodel->brushq3.num_triangles = totaltris;
-       loadmodel->brushq3.data_element3i = (int *)(loadmodel->brushq3.data_color4f + totalverts * 4);
-       loadmodel->brushq3.data_neighbor3i = loadmodel->brushq3.data_element3i + totaltris * 3;
-       totalverts = 0;
-       totaltris = 0;
-       for (i = 0, out = loadmodel->brushq3.data_faces;i < count;i++, out++)
+       i = oldi;
+       out = oldout;
+       for (;i < count;i++, out++)
        {
-               if (!out->type)
+               if(out->num_vertices && out->num_triangles)
                        continue;
-               Con_Printf("totalverts %i, totaltris %i\n", totalverts, totaltris);
-               memcpy(loadmodel->brushq3.data_vertex3f + totalverts * 3, out->data_vertex3f, out->num_vertices * 3 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_texcoordtexture2f + totalverts * 2, out->data_texcoordtexture2f, out->num_vertices * 2 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_texcoordlightmap2f + totalverts * 2, out->data_texcoordlightmap2f, out->num_vertices * 2 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_svector3f + totalverts * 3, out->data_svector3f, out->num_vertices * 3 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_tvector3f + totalverts * 3, out->data_tvector3f, out->num_vertices * 3 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_normal3f + totalverts * 3, out->data_normal3f, out->num_vertices * 3 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_color4f + totalverts * 4, out->data_color4f, out->num_vertices * 4 * sizeof(float));
-               memcpy(loadmodel->brushq3.data_element3i + totaltris * 3, out->data_element3i, out->num_triangles * 3 * sizeof(int));
-               memcpy(loadmodel->brushq3.data_neighbor3i + totaltris * 3, out->data_neighbor3i, out->num_triangles * 3 * sizeof(int));
-               if (out->firstvertex == -1)
-                       Mem_Free(out->data_vertex3f);
-               if (out->firstelement == -1)
-                       Mem_Free(out->data_element3i);
-               out->firstvertex = totalverts;
-               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->firstelement = totaltris * 3;
-               out->data_element3i = loadmodel->brushq3.data_element3i + out->firstelement;
-               out->data_neighbor3i = loadmodel->brushq3.data_neighbor3i + out->firstelement;
-               //for (j = 0;j < out->numtriangles * 3;j++)
-               //      out->data_element3i[j] += totalverts - out->firstvertex;
-               totalverts += out->num_vertices;
-               totaltris += out->num_triangles;
-       }
-       Mem_Free(originaldata_vertex3f);
-       Mem_Free(originaldata_element3i);
+               if(out->num_vertices == 0)
+                       Con_Printf("Mod_Q3BSP_LoadFaces: surface %d has no vertices, ignoring\n", i);
+               if(out->num_triangles == 0)
+                       Con_Printf("Mod_Q3BSP_LoadFaces: surface %d has no triangles, ignoring\n", i);
        }
-       */
+
+       // for per pixel lighting
+       Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, true);
+
+       // generate ushort elements array if possible
+       if (loadmodel->surfmesh.data_element3s)
+               for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++)
+                       loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i];
+
+       // free the no longer needed vertex data
+       loadmodel->brushq3.num_vertices = 0;
+       if (loadmodel->brushq3.data_vertex3f)
+               Mem_Free(loadmodel->brushq3.data_vertex3f);
+       loadmodel->brushq3.data_vertex3f = NULL;
+       loadmodel->brushq3.data_normal3f = NULL;
+       loadmodel->brushq3.data_texcoordtexture2f = NULL;
+       loadmodel->brushq3.data_texcoordlightmap2f = NULL;
+       loadmodel->brushq3.data_color4f = NULL;
+       // free the no longer needed triangle data
+       loadmodel->brushq3.num_triangles = 0;
+       if (loadmodel->brushq3.data_element3i)
+               Mem_Free(loadmodel->brushq3.data_element3i);
+       loadmodel->brushq3.data_element3i = NULL;
+
+       if(patchtess)
+               Mem_Free(patchtess);
 }
 
 static void Mod_Q3BSP_LoadModels(lump_t *l)
 {
        q3dmodel_t *in;
-       q3mmodel_t *out;
+       q3dmodel_t *out;
        int i, j, n, c, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dmodel_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadModels: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (q3dmodel_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
        loadmodel->brushq3.data_models = out;
        loadmodel->brushq3.num_models = count;
@@ -3932,15 +5223,15 @@ static void Mod_Q3BSP_LoadModels(lump_t *l)
                }
                n = LittleLong(in->firstface);
                c = LittleLong(in->numfaces);
-               if (n < 0 || n + c > loadmodel->brushq3.num_faces)
-                       Host_Error("Mod_Q3BSP_LoadModels: invalid face range %i : %i (%i faces)\n", n, n + c, loadmodel->brushq3.num_faces);
-               out->firstface = loadmodel->brushq3.data_faces + n;
+               if (n < 0 || n + c > loadmodel->num_surfaces)
+                       Host_Error("Mod_Q3BSP_LoadModels: invalid face range %i : %i (%i faces)", n, n + c, loadmodel->num_surfaces);
+               out->firstface = n;
                out->numfaces = c;
                n = LittleLong(in->firstbrush);
                c = LittleLong(in->numbrushes);
-               if (n < 0 || n + c > loadmodel->brushq3.num_brushes)
-                       Host_Error("Mod_Q3BSP_LoadModels: invalid brush range %i : %i (%i brushes)\n", n, n + c, loadmodel->brushq3.num_brushes);
-               out->firstbrush = loadmodel->brushq3.data_brushes + n;
+               if (n < 0 || n + c > loadmodel->brush.num_brushes)
+                       Host_Error("Mod_Q3BSP_LoadModels: invalid brush range %i : %i (%i brushes)", n, n + c, loadmodel->brush.num_brushes);
+               out->firstbrush = n;
                out->numbrushes = c;
        }
 }
@@ -3948,155 +5239,142 @@ static void Mod_Q3BSP_LoadModels(lump_t *l)
 static void Mod_Q3BSP_LoadLeafBrushes(lump_t *l)
 {
        int *in;
-       q3mbrush_t **out;
+       int *out;
        int i, n, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (int *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_leafbrushes = out;
-       loadmodel->brushq3.num_leafbrushes = count;
+       loadmodel->brush.data_leafbrushes = out;
+       loadmodel->brush.num_leafbrushes = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
                n = LittleLong(*in);
-               if (n < 0 || n >= loadmodel->brushq3.num_brushes)
-                       Host_Error("Mod_Q3BSP_LoadLeafBrushes: invalid brush index %i (%i brushes)\n", n, loadmodel->brushq3.num_brushes);
-               *out = loadmodel->brushq3.data_brushes + n;
+               if (n < 0 || n >= loadmodel->brush.num_brushes)
+                       Host_Error("Mod_Q3BSP_LoadLeafBrushes: invalid brush index %i (%i brushes)", n, loadmodel->brush.num_brushes);
+               *out = n;
        }
 }
 
 static void Mod_Q3BSP_LoadLeafFaces(lump_t *l)
 {
        int *in;
-       q3mface_t **out;
+       int *out;
        int i, n, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (int *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_leaffaces = out;
-       loadmodel->brushq3.num_leaffaces = count;
+       loadmodel->brush.data_leafsurfaces = out;
+       loadmodel->brush.num_leafsurfaces = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
                n = LittleLong(*in);
-               if (n < 0 || n >= loadmodel->brushq3.num_faces)
-                       Host_Error("Mod_Q3BSP_LoadLeafFaces: invalid face index %i (%i faces)\n", n, loadmodel->brushq3.num_faces);
-               *out = loadmodel->brushq3.data_faces + n;
+               if (n < 0 || n >= loadmodel->num_surfaces)
+                       Host_Error("Mod_Q3BSP_LoadLeafFaces: invalid face index %i (%i faces)", n, loadmodel->num_surfaces);
+               *out = n;
        }
 }
 
 static void Mod_Q3BSP_LoadLeafs(lump_t *l)
 {
        q3dleaf_t *in;
-       q3mleaf_t *out;
+       mleaf_t *out;
        int i, j, n, c, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dleaf_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLeafs: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_leafs = out;
-       loadmodel->brushq3.num_leafs = count;
+       loadmodel->brush.data_leafs = out;
+       loadmodel->brush.num_leafs = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
-               out->isnode = false;
                out->parent = NULL;
+               out->plane = NULL;
                out->clusterindex = LittleLong(in->clusterindex);
                out->areaindex = LittleLong(in->areaindex);
                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]);
+                       out->mins[j] = LittleLong(in->mins[j]) - 1;
+                       out->maxs[j] = LittleLong(in->maxs[j]) + 1;
                }
                n = LittleLong(in->firstleafface);
                c = LittleLong(in->numleaffaces);
-               if (n < 0 || n + c > loadmodel->brushq3.num_leaffaces)
-                       Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafface range %i : %i (%i leaffaces)\n", n, n + c, loadmodel->brushq3.num_leaffaces);
-               out->firstleafface = loadmodel->brushq3.data_leaffaces + n;
-               out->numleaffaces = c;
+               if (n < 0 || n + c > loadmodel->brush.num_leafsurfaces)
+                       Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafsurface range %i : %i (%i leafsurfaces)", n, n + c, loadmodel->brush.num_leafsurfaces);
+               out->firstleafsurface = loadmodel->brush.data_leafsurfaces + n;
+               out->numleafsurfaces = c;
                n = LittleLong(in->firstleafbrush);
                c = LittleLong(in->numleafbrushes);
-               if (n < 0 || n + c > loadmodel->brushq3.num_leafbrushes)
-                       Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafbrush range %i : %i (%i leafbrushes)\n", n, n + c, loadmodel->brushq3.num_leafbrushes);
-               out->firstleafbrush = loadmodel->brushq3.data_leafbrushes + n;
+               if (n < 0 || n + c > loadmodel->brush.num_leafbrushes)
+                       Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafbrush range %i : %i (%i leafbrushes)", n, n + c, loadmodel->brush.num_leafbrushes);
+               out->firstleafbrush = loadmodel->brush.data_leafbrushes + n;
                out->numleafbrushes = c;
        }
 }
 
-static void Mod_Q3BSP_LoadNodes_RecursiveSetParent(q3mnode_t *node, q3mnode_t *parent)
-{
-       if (node->parent)
-               Host_Error("Mod_Q3BSP_LoadNodes_RecursiveSetParent: runaway recursion\n");
-       node->parent = parent;
-       if (node->isnode)
-       {
-               Mod_Q3BSP_LoadNodes_RecursiveSetParent(node->children[0], node);
-               Mod_Q3BSP_LoadNodes_RecursiveSetParent(node->children[1], node);
-       }
-}
-
 static void Mod_Q3BSP_LoadNodes(lump_t *l)
 {
        q3dnode_t *in;
-       q3mnode_t *out;
+       mnode_t *out;
        int i, j, n, count;
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dnode_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadNodes: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+       out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
-       loadmodel->brushq3.data_nodes = out;
-       loadmodel->brushq3.num_nodes = count;
+       loadmodel->brush.data_nodes = out;
+       loadmodel->brush.num_nodes = count;
 
        for (i = 0;i < count;i++, in++, out++)
        {
-               out->isnode = true;
                out->parent = NULL;
                n = LittleLong(in->planeindex);
-               if (n < 0 || n >= loadmodel->brushq3.num_planes)
-                       Host_Error("Mod_Q3BSP_LoadNodes: invalid planeindex %i (%i planes)\n", n, loadmodel->brushq3.num_planes);
-               out->plane = loadmodel->brushq3.data_planes + n;
+               if (n < 0 || n >= loadmodel->brush.num_planes)
+                       Host_Error("Mod_Q3BSP_LoadNodes: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes);
+               out->plane = loadmodel->brush.data_planes + n;
                for (j = 0;j < 2;j++)
                {
                        n = LittleLong(in->childrenindex[j]);
                        if (n >= 0)
                        {
-                               if (n >= loadmodel->brushq3.num_nodes)
-                                       Host_Error("Mod_Q3BSP_LoadNodes: invalid child node index %i (%i nodes)\n", n, loadmodel->brushq3.num_nodes);
-                               out->children[j] = loadmodel->brushq3.data_nodes + n;
+                               if (n >= loadmodel->brush.num_nodes)
+                                       Host_Error("Mod_Q3BSP_LoadNodes: invalid child node index %i (%i nodes)", n, loadmodel->brush.num_nodes);
+                               out->children[j] = loadmodel->brush.data_nodes + n;
                        }
                        else
                        {
                                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);
+                               if (n >= loadmodel->brush.num_leafs)
+                                       Host_Error("Mod_Q3BSP_LoadNodes: invalid child leaf index %i (%i leafs)", n, loadmodel->brush.num_leafs);
+                               out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + n);
                        }
                }
                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]);
+                       out->mins[j] = LittleLong(in->mins[j]) - 1;
+                       out->maxs[j] = LittleLong(in->maxs[j]) + 1;
                }
        }
 
        // set the parent pointers
-       Mod_Q3BSP_LoadNodes_RecursiveSetParent(loadmodel->brushq3.data_nodes, NULL);
+       Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL);
 }
 
 static void Mod_Q3BSP_LoadLightGrid(lump_t *l)
@@ -4105,39 +5383,38 @@ static void Mod_Q3BSP_LoadLightGrid(lump_t *l)
        q3dlightgrid_t *out;
        int count;
 
-       if (l->filelen == 0)
-               return;
-
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dlightgrid_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
                Host_Error("Mod_Q3BSP_LoadLightGrid: funny lump size in %s",loadmodel->name);
        loadmodel->brushq3.num_lightgrid_scale[0] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[0];
        loadmodel->brushq3.num_lightgrid_scale[1] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[1];
        loadmodel->brushq3.num_lightgrid_scale[2] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[2];
-       loadmodel->brushq3.num_lightgrid_imins[0] = ceil(loadmodel->brushq3.data_models->mins[0] * loadmodel->brushq3.num_lightgrid_scale[0]);
-       loadmodel->brushq3.num_lightgrid_imins[1] = ceil(loadmodel->brushq3.data_models->mins[1] * loadmodel->brushq3.num_lightgrid_scale[1]);
-       loadmodel->brushq3.num_lightgrid_imins[2] = ceil(loadmodel->brushq3.data_models->mins[2] * loadmodel->brushq3.num_lightgrid_scale[2]);
-       loadmodel->brushq3.num_lightgrid_imaxs[0] = floor(loadmodel->brushq3.data_models->maxs[0] * loadmodel->brushq3.num_lightgrid_scale[0]);
-       loadmodel->brushq3.num_lightgrid_imaxs[1] = floor(loadmodel->brushq3.data_models->maxs[1] * loadmodel->brushq3.num_lightgrid_scale[1]);
-       loadmodel->brushq3.num_lightgrid_imaxs[2] = floor(loadmodel->brushq3.data_models->maxs[2] * loadmodel->brushq3.num_lightgrid_scale[2]);
+       loadmodel->brushq3.num_lightgrid_imins[0] = (int)ceil(loadmodel->brushq3.data_models->mins[0] * loadmodel->brushq3.num_lightgrid_scale[0]);
+       loadmodel->brushq3.num_lightgrid_imins[1] = (int)ceil(loadmodel->brushq3.data_models->mins[1] * loadmodel->brushq3.num_lightgrid_scale[1]);
+       loadmodel->brushq3.num_lightgrid_imins[2] = (int)ceil(loadmodel->brushq3.data_models->mins[2] * loadmodel->brushq3.num_lightgrid_scale[2]);
+       loadmodel->brushq3.num_lightgrid_imaxs[0] = (int)floor(loadmodel->brushq3.data_models->maxs[0] * loadmodel->brushq3.num_lightgrid_scale[0]);
+       loadmodel->brushq3.num_lightgrid_imaxs[1] = (int)floor(loadmodel->brushq3.data_models->maxs[1] * loadmodel->brushq3.num_lightgrid_scale[1]);
+       loadmodel->brushq3.num_lightgrid_imaxs[2] = (int)floor(loadmodel->brushq3.data_models->maxs[2] * loadmodel->brushq3.num_lightgrid_scale[2]);
        loadmodel->brushq3.num_lightgrid_isize[0] = loadmodel->brushq3.num_lightgrid_imaxs[0] - loadmodel->brushq3.num_lightgrid_imins[0] + 1;
        loadmodel->brushq3.num_lightgrid_isize[1] = loadmodel->brushq3.num_lightgrid_imaxs[1] - loadmodel->brushq3.num_lightgrid_imins[1] + 1;
        loadmodel->brushq3.num_lightgrid_isize[2] = loadmodel->brushq3.num_lightgrid_imaxs[2] - loadmodel->brushq3.num_lightgrid_imins[2] + 1;
        count = loadmodel->brushq3.num_lightgrid_isize[0] * loadmodel->brushq3.num_lightgrid_isize[1] * loadmodel->brushq3.num_lightgrid_isize[2];
-       if (l->filelen < count * (int)sizeof(*in))
-               Host_Error("Mod_Q3BSP_LoadLightGrid: invalid lightgrid lump size %i bytes, should be %i bytes (%ix%ix%i)\n", l->filelen, count * sizeof(*in), loadmodel->brushq3.num_lightgrid_dimensions[0], loadmodel->brushq3.num_lightgrid_dimensions[1], loadmodel->brushq3.num_lightgrid_dimensions[2]);
-       if (l->filelen != count * (int)sizeof(*in))
-               Con_Printf("Mod_Q3BSP_LoadLightGrid: Warning: calculated lightgrid size %i bytes does not match lump size %i\n", count * sizeof(*in), l->filelen);
-
-       out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
-       loadmodel->brushq3.data_lightgrid = out;
-       loadmodel->brushq3.num_lightgrid = count;
-
-       // no swapping or validation necessary
-       memcpy(out, in, count * (int)sizeof(*out));
-
        Matrix4x4_CreateScale3(&loadmodel->brushq3.num_lightgrid_indexfromworld, loadmodel->brushq3.num_lightgrid_scale[0], loadmodel->brushq3.num_lightgrid_scale[1], loadmodel->brushq3.num_lightgrid_scale[2]);
        Matrix4x4_ConcatTranslate(&loadmodel->brushq3.num_lightgrid_indexfromworld, -loadmodel->brushq3.num_lightgrid_imins[0] * loadmodel->brushq3.num_lightgrid_cellsize[0], -loadmodel->brushq3.num_lightgrid_imins[1] * loadmodel->brushq3.num_lightgrid_cellsize[1], -loadmodel->brushq3.num_lightgrid_imins[2] * loadmodel->brushq3.num_lightgrid_cellsize[2]);
+
+       // if lump is empty there is nothing to load, we can deal with that in the LightPoint code
+       if (l->filelen)
+       {
+               if (l->filelen < count * (int)sizeof(*in))
+                       Host_Error("Mod_Q3BSP_LoadLightGrid: invalid lightgrid lump size %i bytes, should be %i bytes (%ix%ix%i)", l->filelen, (int)(count * sizeof(*in)), loadmodel->brushq3.num_lightgrid_isize[0], loadmodel->brushq3.num_lightgrid_isize[1], loadmodel->brushq3.num_lightgrid_isize[2]);
+               if (l->filelen != count * (int)sizeof(*in))
+                       Con_Printf("Mod_Q3BSP_LoadLightGrid: Warning: calculated lightgrid size %i bytes does not match lump size %i\n", (int)(count * sizeof(*in)), l->filelen);
+               out = (q3dlightgrid_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
+               loadmodel->brushq3.data_lightgrid = out;
+               loadmodel->brushq3.num_lightgrid = count;
+               // no swapping or validation necessary
+               memcpy(out, in, count * (int)sizeof(*out));
+       }
 }
 
 static void Mod_Q3BSP_LoadPVS(lump_t *l)
@@ -4146,43 +5423,55 @@ static void Mod_Q3BSP_LoadPVS(lump_t *l)
        int totalchains;
 
        if (l->filelen == 0)
+       {
+               int i;
+               // unvised maps often have cluster indices even without pvs, so check
+               // leafs to find real number of clusters
+               loadmodel->brush.num_pvsclusters = 1;
+               for (i = 0;i < loadmodel->brush.num_leafs;i++)
+                       loadmodel->brush.num_pvsclusters = max(loadmodel->brush.num_pvsclusters, loadmodel->brush.data_leafs[i].clusterindex + 1);
+
+               // create clusters
+               loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters + 7) / 8;
+               totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters;
+               loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains);
+               memset(loadmodel->brush.data_pvsclusters, 0xFF, totalchains);
                return;
+       }
 
-       in = (void *)(mod_base + l->fileofs);
+       in = (q3dpvs_t *)(mod_base + l->fileofs);
        if (l->filelen < 9)
                Host_Error("Mod_Q3BSP_LoadPVS: funny lump size in %s",loadmodel->name);
 
-       loadmodel->brushq3.num_pvsclusters = LittleLong(in->numclusters);
-       loadmodel->brushq3.num_pvschainlength = LittleLong(in->chainlength);
-       if (loadmodel->brushq3.num_pvschainlength < ((loadmodel->brushq3.num_pvsclusters + 7) / 8))
-               Host_Error("Mod_Q3BSP_LoadPVS: (chainlength = %i) < ((numclusters = %i) + 7) / 8\n", loadmodel->brushq3.num_pvschainlength, loadmodel->brushq3.num_pvsclusters);
-       totalchains = loadmodel->brushq3.num_pvschainlength * loadmodel->brushq3.num_pvsclusters;
+       loadmodel->brush.num_pvsclusters = LittleLong(in->numclusters);
+       loadmodel->brush.num_pvsclusterbytes = LittleLong(in->chainlength);
+       if (loadmodel->brush.num_pvsclusterbytes < ((loadmodel->brush.num_pvsclusters + 7) / 8))
+               Host_Error("Mod_Q3BSP_LoadPVS: (chainlength = %i) < ((numclusters = %i) + 7) / 8", loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.num_pvsclusters);
+       totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters;
        if (l->filelen < totalchains + (int)sizeof(*in))
-               Host_Error("Mod_Q3BSP_LoadPVS: lump too small ((numclusters = %i) * (chainlength = %i) + sizeof(q3dpvs_t) == %i bytes, lump is %i bytes)\n", loadmodel->brushq3.num_pvsclusters, loadmodel->brushq3.num_pvschainlength, totalchains + sizeof(*in), l->filelen);
-
-       loadmodel->brushq3.data_pvschains = Mem_Alloc(loadmodel->mempool, totalchains);
-       memcpy(loadmodel->brushq3.data_pvschains, (qbyte *)(in + 1), totalchains);
-}
+               Host_Error("Mod_Q3BSP_LoadPVS: lump too small ((numclusters = %i) * (chainlength = %i) + sizeof(q3dpvs_t) == %i bytes, lump is %i bytes)", loadmodel->brush.num_pvsclusters, loadmodel->brush.num_pvsclusterbytes, (int)(totalchains + sizeof(*in)), l->filelen);
 
-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);
+       loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains);
+       memcpy(loadmodel->brush.data_pvsclusters, (unsigned char *)(in + 1), totalchains);
 }
 
-static void Mod_Q3BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal)
+static void Mod_Q3BSP_LightPoint(dp_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;
+       float transformed[3], blend1, blend2, blend, stylescale;
        q3dlightgrid_t *a, *s;
-       // FIXME: write this
+
+       // scale lighting by lightstyle[0] so that darkmode in dpmod works properly
+       stylescale = r_refdef.scene.rtlightstylevalue[0];
+
        if (!model->brushq3.num_lightgrid)
        {
-               ambientcolor[0] = 1;
-               ambientcolor[1] = 1;
-               ambientcolor[2] = 1;
+               ambientcolor[0] = stylescale;
+               ambientcolor[1] = stylescale;
+               ambientcolor[2] = stylescale;
                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]);
@@ -4193,6 +5482,7 @@ static void Mod_Q3BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientc
        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]];
@@ -4208,41 +5498,77 @@ static void Mod_Q3BSP_LightPoint(model_t *model, const vec3_t p, vec3_t ambientc
                                continue;
                        for (i = 0;i < 2;i++)
                        {
-                               blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0])));
+                               blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0]))) * stylescale;
                                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));
+                               // this uses the mod_md3_sin table because the values are
+                               // already in the 0-255 range, the 64+ bias fetches a cosine
+                               // instead of a sine value
+                               diffusenormal[0] += blend * (mod_md3_sin[64 + s->diffuseyaw] * mod_md3_sin[s->diffusepitch]);
+                               diffusenormal[1] += blend * (mod_md3_sin[     s->diffuseyaw] * mod_md3_sin[s->diffusepitch]);
+                               diffusenormal[2] += blend * (mod_md3_sin[64 + s->diffusepitch]);
                                //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)));
                        }
                }
        }
+
+       // normalize the light direction before turning
        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)
+static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe)
+{
+       int i;
+       mleaf_t *leaf;
+       colbrushf_t *brush;
+       // find which leaf the point is in
+       while (node->plane)
+               node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist];
+       // point trace the brushes
+       leaf = (mleaf_t *)node;
+       for (i = 0;i < leaf->numleafbrushes;i++)
+       {
+               brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf;
+               if (brush && brush->markframe != markframe && BoxesOverlap(point, point, brush->mins, brush->maxs))
+               {
+                       brush->markframe = markframe;
+                       Collision_TracePointBrushFloat(trace, point, brush);
+               }
+       }
+       // can't do point traces on curves (they have no thickness)
+}
+
+static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_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, const vec3_t segmentmins, const vec3_t segmentmaxs)
 {
        int i, startside, endside;
-       float dist1, dist2, midfrac, mid[3], segmentmins[3], segmentmaxs[3];
-       q3mleaf_t *leaf;
-       q3mface_t *face;
+       float dist1, dist2, midfrac, mid[3], nodesegmentmins[3], nodesegmentmaxs[3];
+       mleaf_t *leaf;
+       msurface_t *surface;
+       mplane_t *plane;
        colbrushf_t *brush;
-       if (startfrac >= trace->fraction)
-               return;
-       // note: all line fragments past first impact fraction are ignored
-       while (node->isnode)
+       // walk the tree until we hit a leaf, recursing for any split cases
+       while (node->plane)
        {
-               // recurse down node sides
-               dist1 = PlaneDiff(start, node->plane);
-               dist2 = PlaneDiff(end, node->plane);
+               // abort if this part of the bsp tree can not be hit by this trace
+//             if (!(node->combinedsupercontents & trace->hitsupercontentsmask))
+//                     return;
+               plane = node->plane;
+               // axial planes are much more common than non-axial, so an optimized
+               // axial case pays off here
+               if (plane->type < 3)
+               {
+                       dist1 = start[plane->type] - plane->dist;
+                       dist2 = end[plane->type] - plane->dist;
+               }
+               else
+               {
+                       dist1 = DotProduct(start, plane->normal) - plane->dist;
+                       dist2 = DotProduct(end, plane->normal) - plane->dist;
+               }
                startside = dist1 < 0;
                endside = dist2 < 0;
                if (startside == endside)
@@ -4253,377 +5579,426 @@ static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, q3mnode_t *node
                else
                {
                        // line crosses node plane, split the line
+                       dist1 = PlaneDiff(linestart, plane);
+                       dist2 = PlaneDiff(lineend, plane);
                        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);
+                       Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[startside], start, mid, startfrac, midfrac, linestart, lineend, markframe, segmentmins, segmentmaxs);
+                       // if we found an impact on the front side, don't waste time
+                       // exploring the far side
+                       if (midfrac <= trace->realfraction)
+                               Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[endside], mid, end, midfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs);
                        return;
                }
        }
+       // abort if this part of the bsp tree can not be hit by this trace
+//     if (!(node->combinedsupercontents & trace->hitsupercontentsmask))
+//             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;
+       nodesegmentmins[0] = min(start[0], end[0]) - 1;
+       nodesegmentmins[1] = min(start[1], end[1]) - 1;
+       nodesegmentmins[2] = min(start[2], end[2]) - 1;
+       nodesegmentmaxs[0] = max(start[0], end[0]) + 1;
+       nodesegmentmaxs[1] = max(start[1], end[1]) + 1;
+       nodesegmentmaxs[2] = max(start[2], end[2]) + 1;
+       // line trace the brushes
+       leaf = (mleaf_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 = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf;
+               if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs))
                {
                        brush->markframe = markframe;
-                       if (BoxesOverlap(segmentmins, segmentmaxs, brush->mins, brush->maxs))
-                               Collision_TraceLineBrushFloat(trace, linestart, lineend, leaf->firstleafbrush[i]->colbrushf, leaf->firstleafbrush[i]->colbrushf);
+                       Collision_TraceLineBrushFloat(trace, linestart, lineend, brush, brush);
                }
        }
-       if (mod_q3bsp_curves_collisions.integer)
+       // can't do point traces on curves (they have no thickness)
+       if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer && !VectorCompare(start, end))
        {
-               for (i = 0;i < leaf->numleaffaces;i++)
+               // line trace the curves
+               for (i = 0;i < leaf->numleafsurfaces;i++)
                {
-                       if (startfrac >= trace->fraction)
-                               return;
-                       face = leaf->firstleafface[i];
-                       if (face->collisions && face->collisionmarkframe != markframe)
+                       surface = model->data_surfaces + leaf->firstleafsurface[i];
+                       if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
                        {
-                               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);
+                               surface->collisionmarkframe = markframe;
+                               Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, 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)
+static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_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;
+       int i;
+       int sides;
+       mleaf_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)
+       msurface_t *surface;
+       mplane_t *plane;
+       float nodesegmentmins[3], nodesegmentmaxs[3];
+       // walk the tree until we hit a leaf, recursing for any split cases
+       while (node->plane)
        {
-               // recurse down node sides
-               sides = BoxOnPlaneSide(segmentmins, segmentmaxs, node->plane);
-               if (sides == 3)
+               // abort if this part of the bsp tree can not be hit by this trace
+//             if (!(node->combinedsupercontents & trace->hitsupercontentsmask))
+//                     return;
+               plane = node->plane;
+               // axial planes are much more common than non-axial, so an optimized
+               // axial case pays off here
+               if (plane->type < 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);
+                       // this is an axial plane, compare bounding box directly to it and
+                       // recurse sides accordingly
+                       // recurse down node sides
+                       // use an inlined axial BoxOnPlaneSide to slightly reduce overhead
+                       //sides = BoxOnPlaneSide(nodesegmentmins, nodesegmentmaxs, plane);
+                       //sides = ((segmentmaxs[plane->type] >= plane->dist) | ((segmentmins[plane->type] < plane->dist) << 1));
+                       sides = ((segmentmaxs[plane->type] >= plane->dist) + ((segmentmins[plane->type] < plane->dist) * 2));
                }
-               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++)
+               else
                {
-                       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, markframe, nodesegmentmins, nodesegmentmaxs);
-                               break;
-                       }
+                       // this is a non-axial plane, so check if the start and end boxes
+                       // are both on one side of the plane to handle 'diagonal' cases
+                       sides = BoxOnPlaneSide(thisbrush_start->mins, thisbrush_start->maxs, plane) | BoxOnPlaneSide(thisbrush_end->mins, thisbrush_end->maxs, plane);
                }
-               */
-               /*
-               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 (sides == 3)
                {
-                       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, markframe, nodesegmentmins, nodesegmentmaxs);
-                               break;
-                       }
+                       // segment crosses plane
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs);
+                       sides = 2;
                }
-               */
-               /*
-               sides = BoxOnPlaneSide(boxstartmins, boxstartmaxs, node->plane) | BoxOnPlaneSide(boxendmins, boxendmaxs, node->plane);
-               if (sides & 1)
-                       Mod_Q3BSP_TraceBox_RecursiveBSPNode(trace, node->children[0], boxstartmins, boxstartmaxs, boxendmins, boxendmaxs);
-               if (sides & 2)
-                       Mod_Q3BSP_TraceBox_RecursiveBSPNode(trace, node->children[1], boxstartmins, boxstartmaxs, boxendmins, boxendmaxs);
-               */
+               // if sides == 0 then the trace itself is bogus (Not A Number values),
+               // in this case we simply pretend the trace hit nothing
+               if (sides == 0)
+                       return; // ERROR: NAN bounding box!
+               // take whichever side the segment box is on
+               node = node->children[sides - 1];
        }
-       else
+       // abort if this part of the bsp tree can not be hit by this trace
+//     if (!(node->combinedsupercontents & trace->hitsupercontentsmask))
+//             return;
+       nodesegmentmins[0] = max(segmentmins[0], node->mins[0] - 1);
+       nodesegmentmins[1] = max(segmentmins[1], node->mins[1] - 1);
+       nodesegmentmins[2] = max(segmentmins[2], node->mins[2] - 1);
+       nodesegmentmaxs[0] = min(segmentmaxs[0], node->maxs[0] + 1);
+       nodesegmentmaxs[1] = min(segmentmaxs[1], node->maxs[1] + 1);
+       nodesegmentmaxs[2] = min(segmentmaxs[2], node->maxs[2] + 1);
+       // hit a leaf
+       leaf = (mleaf_t *)node;
+       for (i = 0;i < leaf->numleafbrushes;i++)
        {
-               // hit a leaf
-               leaf = (q3mleaf_t *)node;
-               for (i = 0;i < leaf->numleafbrushes;i++)
+               brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf;
+               if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs))
                {
-                       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);
-                       }
+                       brush->markframe = markframe;
+                       Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush);
                }
-               if (mod_q3bsp_curves_collisions.integer)
+       }
+       if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer)
+       {
+               for (i = 0;i < leaf->numleafsurfaces;i++)
                {
-                       for (i = 0;i < leaf->numleaffaces;i++)
+                       surface = model->data_surfaces + leaf->firstleafsurface[i];
+                       if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
                        {
-                               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);
+                               surface->collisionmarkframe = markframe;
+                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
                        }
                }
        }
 }
 
-static void Mod_Q3BSP_TraceBox(model_t *model, int frame, trace_t *trace, const vec3_t boxstartmins, const vec3_t boxstartmaxs, const vec3_t boxendmins, const vec3_t boxendmaxs, int hitsupercontentsmask)
+static void Mod_Q3BSP_TraceBox(dp_model_t *model, int frame, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask)
 {
        int i;
+       vec3_t shiftstart, shiftend;
        float segmentmins[3], segmentmaxs[3];
-       colbrushf_t *thisbrush_start, *thisbrush_end;
-       matrix4x4_t startmatrix, endmatrix;
        static int markframe = 0;
-       q3mface_t *face;
+       msurface_t *surface;
+       q3mbrush_t *brush;
        memset(trace, 0, sizeof(*trace));
        trace->fraction = 1;
+       trace->realfraction = 1;
        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)
+       if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(boxmins, boxmaxs))
+       {
+               VectorAdd(start, boxmins, shiftstart);
+               VectorAdd(end, boxmins, shiftend);
+               if (VectorCompare(shiftstart, shiftend))
+               {
+                       // point trace
+                       if (model->brush.submodel)
                        {
-                               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);
-                               }
+                               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                                       if (brush->colbrushf)
+                                               Collision_TracePointBrushFloat(trace, shiftstart, brush->colbrushf);
                        }
+                       else
+                               Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, shiftstart, ++markframe);
                }
                else
-                       Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model->brushq3.data_nodes, boxstartmins, boxendmins, 0, 1, boxstartmins, boxendmins, ++markframe);
+               {
+                       // line trace
+                       segmentmins[0] = min(shiftstart[0], shiftend[0]) - 1;
+                       segmentmins[1] = min(shiftstart[1], shiftend[1]) - 1;
+                       segmentmins[2] = min(shiftstart[2], shiftend[2]) - 1;
+                       segmentmaxs[0] = max(shiftstart[0], shiftend[0]) + 1;
+                       segmentmaxs[1] = max(shiftstart[1], shiftend[1]) + 1;
+                       segmentmaxs[2] = max(shiftstart[2], shiftend[2]) + 1;
+                       if (model->brush.submodel)
+                       {
+                               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                                       if (brush->colbrushf)
+                                               Collision_TraceLineBrushFloat(trace, shiftstart, shiftend, brush->colbrushf, brush->colbrushf);
+                               if (mod_q3bsp_curves_collisions.integer)
+                                       for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++)
+                                               if (surface->num_collisiontriangles)
+                                                       Collision_TraceLineTriangleMeshFloat(trace, shiftstart, shiftend, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
+                       }
+                       else
+                               Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, shiftstart, shiftend, 0, 1, shiftstart, shiftend, ++markframe, segmentmins, segmentmaxs);
+               }
        }
        else
        {
                // box trace, performed as brush trace
-               thisbrush_start = Collision_BrushForBox(&startmatrix, boxstartmins, boxstartmaxs);
-               thisbrush_end = Collision_BrushForBox(&endmatrix, boxendmins, boxendmaxs);
-               if (model->brushq3.submodel)
+               colbrushf_t *thisbrush_start, *thisbrush_end;
+               vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs;
+               segmentmins[0] = min(start[0], end[0]) + boxmins[0] - 1;
+               segmentmins[1] = min(start[1], end[1]) + boxmins[1] - 1;
+               segmentmins[2] = min(start[2], end[2]) + boxmins[2] - 1;
+               segmentmaxs[0] = max(start[0], end[0]) + boxmaxs[0] + 1;
+               segmentmaxs[1] = max(start[1], end[1]) + boxmaxs[1] + 1;
+               segmentmaxs[2] = max(start[2], end[2]) + boxmaxs[2] + 1;
+               VectorAdd(start, boxmins, boxstartmins);
+               VectorAdd(start, boxmaxs, boxstartmaxs);
+               VectorAdd(end, boxmins, boxendmins);
+               VectorAdd(end, boxmaxs, boxendmaxs);
+               thisbrush_start = Collision_BrushForBox(&identitymatrix, boxstartmins, boxstartmaxs, 0, 0, NULL);
+               thisbrush_end = Collision_BrushForBox(&identitymatrix, boxendmins, boxendmaxs, 0, 0, NULL);
+               if (model->brush.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);
+                       for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                               if (brush->colbrushf)
+                                       Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush->colbrushf, brush->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);
-                               }
-                       }
+                               for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++)
+                                       if (surface->num_collisiontriangles)
+                                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
                }
                else
-                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model->brushq3.data_nodes, thisbrush_start, thisbrush_end, ++markframe, segmentmins, segmentmaxs);
+                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, model->brush.data_nodes, thisbrush_start, thisbrush_end, ++markframe, segmentmins, segmentmaxs);
        }
 }
 
-
-static int Mod_Q3BSP_BoxTouchingPVS_RecursiveBSPNode(const model_t *model, const q3mnode_t *node, const qbyte *pvs, const vec3_t mins, const vec3_t maxs)
+static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point)
 {
-       int clusterindex;
-loc0:
-       if (!node->isnode)
-       {
-               // leaf
-               clusterindex = ((q3mleaf_t *)node)->clusterindex;
-               return pvs[clusterindex >> 3] & (1 << (clusterindex & 7));
-       }
-
-       // node - recurse down the BSP tree
-       switch (BoxOnPlaneSide(mins, maxs, node->plane))
+       int i;
+       int supercontents = 0;
+       q3mbrush_t *brush;
+       // test if the point is inside each brush
+       if (model->brush.submodel)
        {
-       case 1: // front
-               node = node->children[0];
-               goto loc0;
-       case 2: // back
-               node = node->children[1];
-               goto loc0;
-       default: // crossing
-               if (Mod_Q3BSP_BoxTouchingPVS_RecursiveBSPNode(model, node->children[0], pvs, mins, maxs))
-                       return true;
-               node = node->children[1];
-               goto loc0;
+               // submodels are effectively one leaf
+               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                       if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf))
+                               supercontents |= brush->colbrushf->supercontents;
        }
-       // never reached
-       return false;
-}
-
-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);
-}
-
-//Returns PVS data for a given point
-//(note: can return NULL)
-static qbyte *Mod_Q3BSP_GetPVS(model_t *model, const vec3_t p)
-{
-       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
+               mnode_t *node = model->brush.data_nodes;
+               mleaf_t *leaf;
+               // find which leaf the point is in
+               while (node->plane)
+                       node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist];
+               leaf = (mleaf_t *)node;
+               // now check the brushes in the leaf
+               for (i = 0;i < leaf->numleafbrushes;i++)
                {
-                       // go down both sides
-                       Mod_Q3BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]);
-                       node = node->children[1];
+                       brush = model->brush.data_brushes + leaf->firstleafbrush[i];
+                       if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf))
+                               supercontents |= brush->colbrushf->supercontents;
                }
        }
-       // 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;
+       return supercontents;
 }
 
-
-static int Mod_Q3BSP_SuperContentsFromNativeContents(model_t *model, int nativecontents)
+static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents)
 {
        int supercontents = 0;
-       if (nativecontents & Q2CONTENTS_SOLID)
+       if (nativecontents & CONTENTSQ3_SOLID)
                supercontents |= SUPERCONTENTS_SOLID;
-       if (nativecontents & Q2CONTENTS_WATER)
+       if (nativecontents & CONTENTSQ3_WATER)
                supercontents |= SUPERCONTENTS_WATER;
-       if (nativecontents & Q2CONTENTS_SLIME)
+       if (nativecontents & CONTENTSQ3_SLIME)
                supercontents |= SUPERCONTENTS_SLIME;
-       if (nativecontents & Q2CONTENTS_LAVA)
+       if (nativecontents & CONTENTSQ3_LAVA)
                supercontents |= SUPERCONTENTS_LAVA;
+       if (nativecontents & CONTENTSQ3_BODY)
+               supercontents |= SUPERCONTENTS_BODY;
+       if (nativecontents & CONTENTSQ3_CORPSE)
+               supercontents |= SUPERCONTENTS_CORPSE;
+       if (nativecontents & CONTENTSQ3_NODROP)
+               supercontents |= SUPERCONTENTS_NODROP;
+       if (nativecontents & CONTENTSQ3_PLAYERCLIP)
+               supercontents |= SUPERCONTENTS_PLAYERCLIP;
+       if (nativecontents & CONTENTSQ3_MONSTERCLIP)
+               supercontents |= SUPERCONTENTS_MONSTERCLIP;
+       if (nativecontents & CONTENTSQ3_DONOTENTER)
+               supercontents |= SUPERCONTENTS_DONOTENTER;
+       if (nativecontents & CONTENTSQ3_BOTCLIP)
+               supercontents |= SUPERCONTENTS_BOTCLIP;
+       if (!(nativecontents & CONTENTSQ3_TRANSLUCENT))
+               supercontents |= SUPERCONTENTS_OPAQUE;
        return supercontents;
 }
 
-static int Mod_Q3BSP_NativeContentsFromSuperContents(model_t *model, int supercontents)
+static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents)
 {
        int nativecontents = 0;
        if (supercontents & SUPERCONTENTS_SOLID)
-               nativecontents |= Q2CONTENTS_SOLID;
+               nativecontents |= CONTENTSQ3_SOLID;
        if (supercontents & SUPERCONTENTS_WATER)
-               nativecontents |= Q2CONTENTS_WATER;
+               nativecontents |= CONTENTSQ3_WATER;
        if (supercontents & SUPERCONTENTS_SLIME)
-               nativecontents |= Q2CONTENTS_SLIME;
+               nativecontents |= CONTENTSQ3_SLIME;
        if (supercontents & SUPERCONTENTS_LAVA)
-               nativecontents |= Q2CONTENTS_LAVA;
+               nativecontents |= CONTENTSQ3_LAVA;
+       if (supercontents & SUPERCONTENTS_BODY)
+               nativecontents |= CONTENTSQ3_BODY;
+       if (supercontents & SUPERCONTENTS_CORPSE)
+               nativecontents |= CONTENTSQ3_CORPSE;
+       if (supercontents & SUPERCONTENTS_NODROP)
+               nativecontents |= CONTENTSQ3_NODROP;
+       if (supercontents & SUPERCONTENTS_PLAYERCLIP)
+               nativecontents |= CONTENTSQ3_PLAYERCLIP;
+       if (supercontents & SUPERCONTENTS_MONSTERCLIP)
+               nativecontents |= CONTENTSQ3_MONSTERCLIP;
+       if (supercontents & SUPERCONTENTS_DONOTENTER)
+               nativecontents |= CONTENTSQ3_DONOTENTER;
+       if (supercontents & SUPERCONTENTS_BOTCLIP)
+               nativecontents |= CONTENTSQ3_BOTCLIP;
+       if (!(supercontents & SUPERCONTENTS_OPAQUE))
+               nativecontents |= CONTENTSQ3_TRANSLUCENT;
        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_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)
+void Mod_Q3BSP_RecursiveFindNumLeafs(mnode_t *node)
 {
-       int i, j;
+       int numleafs;
+       while (node->plane)
+       {
+               Mod_Q3BSP_RecursiveFindNumLeafs(node->children[0]);
+               node = node->children[1];
+       }
+       numleafs = ((mleaf_t *)node - loadmodel->brush.data_leafs) + 1;
+       if (loadmodel->brush.num_leafs < numleafs)
+               loadmodel->brush.num_leafs = numleafs;
+}
+
+void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
+{
+       int i, j, numshadowmeshtriangles, lumps;
        q3dheader_t *header;
        float corner[3], yawradius, modelradius;
+       msurface_t *surface;
+
+       mod->modeldatatypestring = "Q3BSP";
 
        mod->type = mod_brushq3;
-       mod->numframes = 1;
+       mod->numframes = 2; // although alternate textures are not supported it is annoying to complain about no such frame 1
        mod->numskins = 1;
 
        header = (q3dheader_t *)buffer;
+       if((char *) bufferend < (char *) buffer + sizeof(q3dheader_t))
+               Host_Error("Mod_Q3BSP_Load: %s is smaller than its header", mod->name);
 
        i = LittleLong(header->version);
-       if (i != Q3BSPVERSION)
+       if (i != Q3BSPVERSION && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE)
                Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION);
-       if (loadmodel->isworldmodel)
-       {
-               Cvar_SetValue("halflifebsp", false);
-               // until we get a texture for it...
-               R_ResetQuakeSky();
-       }
 
        mod->soundfromcenter = true;
        mod->TraceBox = Mod_Q3BSP_TraceBox;
+       mod->PointSuperContents = Mod_Q3BSP_PointSuperContents;
+       mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight;
        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.GetPVS = Mod_Q1BSP_GetPVS;
+       mod->brush.FatPVS = Mod_Q1BSP_FatPVS;
+       mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS;
+       mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS;
+       mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs;
+       mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters;
        mod->brush.LightPoint = Mod_Q3BSP_LightPoint;
-       mod->brush.FindNonSolidLocation = Mod_Q3BSP_FindNonSolidLocation;
-       //mod->DrawSky = R_Q3BSP_DrawSky;
-       mod->Draw = R_Q3BSP_Draw;
-       mod->DrawShadowVolume = R_Q3BSP_DrawShadowVolume;
-       mod->DrawLight = R_Q3BSP_DrawLight;
-
-       mod_base = (qbyte *)header;
+       mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
+       mod->brush.AmbientSoundLevelsForPoint = NULL;
+       mod->brush.RoundUpToHullSize = NULL;
+       mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf;
+       mod->Draw = R_Q1BSP_Draw;
+       mod->DrawDepth = R_Q1BSP_DrawDepth;
+       mod->DrawDebug = R_Q1BSP_DrawDebug;
+       mod->GetLightInfo = R_Q1BSP_GetLightInfo;
+       mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+       mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+       mod->DrawLight = R_Q1BSP_DrawLight;
+
+       mod_base = (unsigned char *)header;
 
        // swap all the lumps
-       for (i = 0;i < (int) sizeof(*header) / 4;i++)
-               ((int *)header)[i] = LittleLong(((int *)header)[i]);
+       header->ident = LittleLong(header->ident);
+       header->version = LittleLong(header->version);
+       lumps = (header->version == Q3BSPVERSION_LIVE) ? Q3HEADER_LUMPS_LIVE : Q3HEADER_LUMPS;
+       for (i = 0;i < lumps;i++)
+       {
+               j = (header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs));
+               if((char *) bufferend < (char *) buffer + j)
+                       Host_Error("Mod_Q3BSP_Load: %s has a lump that starts outside the file!", mod->name);
+               j += (header->lumps[i].filelen = LittleLong(header->lumps[i].filelen));
+               if((char *) bufferend < (char *) buffer + j)
+                       Host_Error("Mod_Q3BSP_Load: %s has a lump that ends outside the file!", mod->name);
+       }
+       /*
+        * NO, do NOT clear them!
+        * they contain actual data referenced by other stuff.
+        * Instead, before using the advertisements lump, check header->versio
+        * again!
+        * Sorry, but otherwise it breaks memory of the first lump.
+       for (i = lumps;i < Q3HEADER_LUMPS_MAX;i++)
+       {
+               header->lumps[i].fileofs = 0;
+               header->lumps[i].filelen = 0;
+       }
+       */
+
+       mod->brush.qw_md4sum = 0;
+       mod->brush.qw_md4sum2 = 0;
+       for (i = 0;i < lumps;i++)
+       {
+               if (i == Q3LUMP_ENTITIES)
+                       continue;
+               mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen);
+               if (i == Q3LUMP_PVS || i == Q3LUMP_LEAFS || i == Q3LUMP_NODES)
+                       continue;
+               mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen);
+
+               // all this checksumming can take a while, so let's send keepalives here too
+               CL_KeepaliveMessage(false);
+       }
 
        Mod_Q3BSP_LoadEntities(&header->lumps[Q3LUMP_ENTITIES]);
        Mod_Q3BSP_LoadTextures(&header->lumps[Q3LUMP_TEXTURES]);
        Mod_Q3BSP_LoadPlanes(&header->lumps[Q3LUMP_PLANES]);
-       Mod_Q3BSP_LoadBrushSides(&header->lumps[Q3LUMP_BRUSHSIDES]);
+       if (header->version == Q3BSPVERSION_IG)
+               Mod_Q3BSP_LoadBrushSides_IG(&header->lumps[Q3LUMP_BRUSHSIDES]);
+       else
+               Mod_Q3BSP_LoadBrushSides(&header->lumps[Q3LUMP_BRUSHSIDES]);
        Mod_Q3BSP_LoadBrushes(&header->lumps[Q3LUMP_BRUSHES]);
        Mod_Q3BSP_LoadEffects(&header->lumps[Q3LUMP_EFFECTS]);
        Mod_Q3BSP_LoadVertices(&header->lumps[Q3LUMP_VERTICES]);
        Mod_Q3BSP_LoadTriangles(&header->lumps[Q3LUMP_TRIANGLES]);
-       Mod_Q3BSP_LoadLightmaps(&header->lumps[Q3LUMP_LIGHTMAPS]);
+       Mod_Q3BSP_LoadLightmaps(&header->lumps[Q3LUMP_LIGHTMAPS], &header->lumps[Q3LUMP_FACES]);
        Mod_Q3BSP_LoadFaces(&header->lumps[Q3LUMP_FACES]);
        Mod_Q3BSP_LoadModels(&header->lumps[Q3LUMP_MODELS]);
        Mod_Q3BSP_LoadLeafBrushes(&header->lumps[Q3LUMP_LEAFBRUSHES]);
@@ -4634,31 +6009,95 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
        Mod_Q3BSP_LoadPVS(&header->lumps[Q3LUMP_PVS]);
        loadmodel->brush.numsubmodels = loadmodel->brushq3.num_models;
 
-       for (i = 0;i < loadmodel->brushq3.num_models;i++)
+       // the MakePortals code works fine on the q3bsp data as well
+       Mod_Q1BSP_MakePortals();
+
+       // FIXME: shader alpha should replace r_wateralpha support in q3bsp
+       loadmodel->brush.supportwateralpha = true;
+
+       // make a single combined shadow mesh to allow optimized shadow volume creation
+       numshadowmeshtriangles = 0;
+       if (cls.state != ca_dedicated)
        {
-               if (i == 0)
-                       mod = loadmodel;
-               else
+               for (j = 0, surface = loadmodel->data_surfaces;j < loadmodel->num_surfaces;j++, surface++)
+               {
+                       surface->num_firstshadowmeshtriangle = numshadowmeshtriangles;
+                       numshadowmeshtriangles += surface->num_triangles;
+               }
+               loadmodel->brush.shadowmesh = Mod_ShadowMesh_Begin(loadmodel->mempool, numshadowmeshtriangles * 3, numshadowmeshtriangles, NULL, NULL, NULL, false, false, true);
+               for (j = 0, surface = loadmodel->data_surfaces;j < loadmodel->num_surfaces;j++, surface++)
+                       if (surface->num_triangles > 0)
+                               Mod_ShadowMesh_AddMesh(loadmodel->mempool, loadmodel->brush.shadowmesh, NULL, NULL, NULL, loadmodel->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle));
+               loadmodel->brush.shadowmesh = Mod_ShadowMesh_Finish(loadmodel->mempool, loadmodel->brush.shadowmesh, false, true, false);
+               Mod_BuildTriangleNeighbors(loadmodel->brush.shadowmesh->neighbor3i, loadmodel->brush.shadowmesh->element3i, loadmodel->brush.shadowmesh->numtriangles);
+       }
+
+       loadmodel->brush.num_leafs = 0;
+       Mod_Q3BSP_RecursiveFindNumLeafs(loadmodel->brush.data_nodes);
+
+       mod = loadmodel;
+       for (i = 0;i < loadmodel->brush.numsubmodels;i++)
+       {
+               if (i > 0)
                {
                        char name[10];
-                       // LordHavoc: only register submodels if it is the world
-                       // (prevents bsp models from replacing world submodels)
-                       if (!loadmodel->isworldmodel)
-                               continue;
                        // duplicate the basic information
-                       sprintf(name, "*%i", i);
-                       mod = Mod_FindName(name);
+                       dpsnprintf(name, sizeof(name), "*%i", i);
+                       mod = Mod_FindName(name, loadmodel->name);
+                       // copy the base model to this one
                        *mod = *loadmodel;
-                       strcpy(mod->name, name);
+                       // rename the clone back to its proper name
+                       strlcpy(mod->name, name, sizeof(mod->name));
+                       mod->brush.parentmodel = loadmodel;
                        // textures and memory belong to the main model
                        mod->texturepool = NULL;
                        mod->mempool = NULL;
+                       mod->brush.TraceLineOfSight = NULL;
+                       mod->brush.GetPVS = NULL;
+                       mod->brush.FatPVS = NULL;
+                       mod->brush.BoxTouchingPVS = NULL;
+                       mod->brush.BoxTouchingLeafPVS = NULL;
+                       mod->brush.BoxTouchingVisibleLeafs = NULL;
+                       mod->brush.FindBoxClusters = NULL;
+                       mod->brush.LightPoint = NULL;
+                       mod->brush.AmbientSoundLevelsForPoint = 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);
+               mod->brush.submodel = i;
+
+               // make the model surface list (used by shadowing/lighting)
+               mod->firstmodelsurface = mod->brushq3.data_models[i].firstface;
+               mod->nummodelsurfaces = mod->brushq3.data_models[i].numfaces;
+               mod->firstmodelbrush = mod->brushq3.data_models[i].firstbrush;
+               mod->nummodelbrushes = mod->brushq3.data_models[i].numbrushes;
+               mod->sortedmodelsurfaces = (int *)Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces));
+               Mod_MakeSortedSurfaces(mod);
+
+               VectorCopy(mod->brushq3.data_models[i].mins, mod->normalmins);
+               VectorCopy(mod->brushq3.data_models[i].maxs, mod->normalmaxs);
+               // enlarge the bounding box to enclose all geometry of this model,
+               // because q3map2 sometimes lies (mostly to affect the lightgrid),
+               // which can in turn mess up the farclip (as well as culling when
+               // outside the level - an unimportant concern)
+
+               //printf("Editing model %d... BEFORE re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]);
+               for (j = 0;j < mod->nummodelsurfaces;j++)
+               {
+                       const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface;
+                       const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex;
+                       int k;
+                       if (!surface->num_vertices)
+                               continue;
+                       for (k = 0;k < surface->num_vertices;k++, v += 3)
+                       {
+                               mod->normalmins[0] = min(mod->normalmins[0], v[0]);
+                               mod->normalmins[1] = min(mod->normalmins[1], v[1]);
+                               mod->normalmins[2] = min(mod->normalmins[2], v[2]);
+                               mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]);
+                               mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]);
+                               mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]);
+                       }
+               }
+               //printf("Editing model %d... AFTER re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]);
                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]));
@@ -4673,27 +6112,73 @@ void Mod_Q3BSP_Load(model_t *mod, void *buffer)
                mod->radius = modelradius;
                mod->radius2 = modelradius * modelradius;
 
-               for (j = 0;j < mod->brushq3.data_thismodel->numfaces;j++)
-                       if (mod->brushq3.data_thismodel->firstface[j].texture->renderflags & Q3MTEXTURERENDERFLAGS_SKY)
+               // this gets altered below if sky or water is used
+               mod->DrawSky = NULL;
+               mod->DrawAddWaterPlanes = NULL;
+
+               for (j = 0;j < mod->nummodelsurfaces;j++)
+                       if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY)
                                break;
-               if (j < mod->brushq3.data_thismodel->numfaces)
-                       mod->DrawSky = R_Q3BSP_DrawSky;
+               if (j < mod->nummodelsurfaces)
+                       mod->DrawSky = R_Q1BSP_DrawSky;
+
+               for (j = 0;j < mod->nummodelsurfaces;j++)
+                       if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION))
+                               break;
+               if (j < mod->nummodelsurfaces)
+                       mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
        }
 }
 
-void Mod_IBSP_Load(model_t *mod, void *buffer)
+void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 {
        int i = LittleLong(((int *)buffer)[1]);
-       if (i == Q3BSPVERSION)
-               Mod_Q3BSP_Load(mod,buffer);
+       if (i == Q3BSPVERSION || i == Q3BSPVERSION_IG || i == Q3BSPVERSION_LIVE)
+               Mod_Q3BSP_Load(mod,buffer, bufferend);
        else if (i == Q2BSPVERSION)
-               Mod_Q2BSP_Load(mod,buffer);
+               Mod_Q2BSP_Load(mod,buffer, bufferend);
        else
-               Host_Error("Mod_IBSP_Load: unknown/unsupported version %i\n", i);
+               Host_Error("Mod_IBSP_Load: unknown/unsupported version %i", i);
+}
+
+void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend)
+{
+       Host_Error("Mod_MAP_Load: not yet implemented");
 }
 
-void Mod_MAP_Load(model_t *mod, void *buffer)
+qboolean Mod_CanSeeBox_Trace(int numsamples, float t, dp_model_t *model, vec3_t eye, vec3_t minsX, vec3_t maxsX)
 {
-       Host_Error("Mod_MAP_Load: not yet implemented\n");
+       // we already have done PVS culling at this point...
+       // so we don't need to do it again.
+
+       int i;
+       vec3_t testorigin, mins, maxs;
+
+       testorigin[0] = (minsX[0] + maxsX[0]) * 0.5;
+       testorigin[1] = (minsX[1] + maxsX[1]) * 0.5;
+       testorigin[2] = (minsX[2] + maxsX[2]) * 0.5;
+
+       if(model->brush.TraceLineOfSight(model, eye, testorigin))
+               return 1;
+
+       // expand the box a little
+       mins[0] = (t+1) * minsX[0] - t * maxsX[0];
+       maxs[0] = (t+1) * maxsX[0] - t * minsX[0];
+       mins[1] = (t+1) * minsX[1] - t * maxsX[1];
+       maxs[1] = (t+1) * maxsX[1] - t * minsX[1];
+       mins[2] = (t+1) * minsX[2] - t * maxsX[2];
+       maxs[2] = (t+1) * maxsX[2] - t * minsX[2];
+
+       for(i = 0; i != numsamples; ++i)
+       {
+               testorigin[0] = lhrandom(mins[0], maxs[0]);
+               testorigin[1] = lhrandom(mins[1], maxs[1]);
+               testorigin[2] = lhrandom(mins[2], maxs[2]);
+
+               if(model->brush.TraceLineOfSight(model, eye, testorigin))
+                       return 1;
+       }
+
+       return 0;
 }