]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_brush.c
implemented a new preallocated "boxbrush" type for use with
[xonotic/darkplaces.git] / model_brush.c
index 2aea6f858bcacc886f9d3382eef556cc056c0a5e..69792ce28d593be543d4f516e32d9d6c465097b1 100644 (file)
@@ -27,22 +27,25 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 
 //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 halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"};
 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", "1", "minimum number of subdivisions (values above 1 will smooth curves that don't need it)"};
+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", "1", "minimum number of subdivisions (values above 1 will smooth curves that don't need it)"};
+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)"};
+cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"};
 
 static texture_t mod_q1bsp_texture_solid;
 static texture_t mod_q1bsp_texture_sky;
@@ -53,7 +56,6 @@ 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_picmipworld);
        Cvar_RegisterVariable(&r_nosurftextures);
@@ -66,9 +68,13 @@ void Mod_BrushInit(void)
        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);
        Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush);
        Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower);
+       Cvar_RegisterVariable(&mod_q3bsp_nolightmaps);
+       Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes);
 
        memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid));
        strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name));
@@ -138,7 +144,7 @@ static int Mod_Q1BSP_FindBoxClusters(dp_model_t *model, const vec3_t mins, const
        mnode_t *node, *nodestack[1024];
        if (!model->brush.num_pvsclusters)
                return -1;
-       node = model->brush.data_nodes;
+       node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
        for (;;)
        {
 #if 1
@@ -203,7 +209,7 @@ static int Mod_Q1BSP_BoxTouchingPVS(dp_model_t *model, const unsigned char *pvs,
        mnode_t *node, *nodestack[1024];
        if (!model->brush.num_pvsclusters)
                return true;
-       node = model->brush.data_nodes;
+       node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
        for (;;)
        {
 #if 1
@@ -274,7 +280,7 @@ static int Mod_Q1BSP_BoxTouchingLeafPVS(dp_model_t *model, const unsigned char *
        mnode_t *node, *nodestack[1024];
        if (!model->brush.num_leafs)
                return true;
-       node = model->brush.data_nodes;
+       node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
        for (;;)
        {
 #if 1
@@ -345,7 +351,7 @@ static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned c
        mnode_t *node, *nodestack[1024];
        if (!model->brush.num_leafs)
                return true;
-       node = model->brush.data_nodes;
+       node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode;
        for (;;)
        {
 #if 1
@@ -411,6 +417,7 @@ static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned c
 typedef struct findnonsolidlocationinfo_s
 {
        vec3_t center;
+       vec3_t absmin, absmax;
        vec_t radius;
        vec3_t nudge;
        vec_t bestdist;
@@ -418,100 +425,128 @@ typedef struct findnonsolidlocationinfo_s
 }
 findnonsolidlocationinfo_t;
 
-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, surfacenum, k, *tri, *mark;
+       int i, *tri;
        float dist, f, vert[3][3], edge[3][3], facenormal[3], edgenormal[3][3], point[3];
+
+       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])
+       {
+               VectorNormalize(facenormal);
+               f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+               if (f <= info->bestdist && f >= -info->bestdist)
+               {
+                       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]))
+                       {
+                               // we got lucky, the center is within the face
+                               dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
+                               if (dist < 0)
+                               {
+                                       dist = -dist;
+                                       if (info->bestdist > dist)
+                                       {
+                                               info->bestdist = dist;
+                                               VectorScale(facenormal, (info->radius - -dist), info->nudge);
+                                       }
+                               }
+                               else
+                               {
+                                       if (info->bestdist > dist)
+                                       {
+                                               info->bestdist = dist;
+                                               VectorScale(facenormal, (info->radius - dist), info->nudge);
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               // check which edge or vertex the center is nearest
+                               for (i = 0;i < 3;i++)
+                               {
+                                       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);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf)
+{
+       int surfacenum, k, *mark;
        msurface_t *surface;
        for (surfacenum = 0, mark = leaf->firstleafsurface;surfacenum < leaf->numleafsurfaces;surfacenum++, mark++)
        {
                surface = info->model->data_surfaces + *mark;
                if (surface->texture->supercontents & SUPERCONTENTS_SOLID)
                {
-                       for (k = 0;k < surface->num_triangles;k++)
+                       if(surface->num_bboxstride)
                        {
-                               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])
+                               int i, cnt, tri;
+                               cnt = (surface->num_triangles + surface->num_bboxstride - 1) / surface->num_bboxstride;
+                               for(i = 0; i < cnt; ++i)
                                {
-                                       VectorNormalize(facenormal);
-                                       f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
-                                       if (f <= info->bestdist && f >= -info->bestdist)
+                                       if(BoxesOverlap(surface->data_bbox6f + i * 6, surface->data_bbox6f + i * 6 + 3, info->absmin, info->absmax))
                                        {
-                                               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]))
-                                               {
-                                                       // we got lucky, the center is within the face
-                                                       dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal);
-                                                       if (dist < 0)
-                                                       {
-                                                               dist = -dist;
-                                                               if (info->bestdist > dist)
-                                                               {
-                                                                       info->bestdist = dist;
-                                                                       VectorScale(facenormal, (info->radius - -dist), info->nudge);
-                                                               }
-                                                       }
-                                                       else
-                                                       {
-                                                               if (info->bestdist > dist)
-                                                               {
-                                                                       info->bestdist = dist;
-                                                                       VectorScale(facenormal, (info->radius - dist), info->nudge);
-                                                               }
-                                                       }
-                                               }
-                                               else
+                                               for(k = 0; k < surface->num_bboxstride; ++k)
                                                {
-                                                       // 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);
-                                                                       }
-                                                               }
-                                                       }
+                                                       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);
+                               }
+                       }
                }
        }
 }
@@ -550,6 +585,14 @@ static void Mod_Q1BSP_FindNonSolidLocation(dp_model_t *model, const vec3_t in, v
        {
                VectorClear(info.nudge);
                info.bestdist = radius;
+               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);
        }
@@ -564,7 +607,7 @@ int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativeconte
                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:
@@ -572,7 +615,7 @@ int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativeconte
                case CONTENTS_LAVA:
                        return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP;
                case CONTENTS_SKY:
-                       return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP;
+                       return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
        }
        return 0;
 }
@@ -810,12 +853,89 @@ static int Mod_Q1BSP_RecursiveHullCheckPoint(RecursiveHullCheckTraceInfo_t *t, i
 }
 //#endif
 
+static void Mod_Q1BSP_TracePoint(struct model_s *model, int frame, trace_t *trace, const vec3_t start, int hitsupercontentsmask)
+{
+       RecursiveHullCheckTraceInfo_t rhc;
+
+       memset(&rhc, 0, sizeof(rhc));
+       memset(trace, 0, sizeof(trace_t));
+       rhc.trace = trace;
+       rhc.trace->fraction = 1;
+       rhc.trace->realfraction = 1;
+       rhc.trace->allsolid = true;
+       rhc.hull = &model->brushq1.hulls[0]; // 0x0x0
+       VectorCopy(start, rhc.start);
+       VectorCopy(start, rhc.end);
+       Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode);
+}
+
+static void Mod_Q1BSP_TraceLine(struct model_s *model, int frame, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask)
+{
+       RecursiveHullCheckTraceInfo_t rhc;
+
+       if (VectorCompare(start, end))
+       {
+               Mod_Q1BSP_TracePoint(model, frame, trace, start, hitsupercontentsmask);
+               return;
+       }
+
+       memset(&rhc, 0, sizeof(rhc));
+       memset(trace, 0, sizeof(trace_t));
+       rhc.trace = trace;
+       rhc.trace->hitsupercontentsmask = hitsupercontentsmask;
+       rhc.trace->fraction = 1;
+       rhc.trace->realfraction = 1;
+       rhc.trace->allsolid = true;
+       rhc.hull = &model->brushq1.hulls[0]; // 0x0x0
+       VectorCopy(start, rhc.start);
+       VectorCopy(end, rhc.end);
+       VectorSubtract(rhc.end, rhc.start, rhc.dist);
+#if COLLISIONPARANOID >= 2
+       Con_Printf("t(%f %f %f,%f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[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 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];
        RecursiveHullCheckTraceInfo_t rhc;
 
+       if (VectorCompare(boxmins, boxmaxs))
+       {
+               if (VectorCompare(start, end))
+                       Mod_Q1BSP_TracePoint(model, frame, trace, start, hitsupercontentsmask);
+               else
+                       Mod_Q1BSP_TraceLine(model, frame, trace, start, end, hitsupercontentsmask);
+               return;
+       }
+
        memset(&rhc, 0, sizeof(rhc));
        memset(trace, 0, sizeof(trace_t));
        rhc.trace = trace;
@@ -902,6 +1022,8 @@ void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cm
 #if 1
        colbrushf_t cbox;
        colplanef_t cbox_planes[6];
+       cbox.isaabb = true;
+       cbox.hasaabbplanes = true;
        cbox.supercontents = boxsupercontents;
        cbox.numplanes = 6;
        cbox.numpoints = 0;
@@ -1001,74 +1123,27 @@ void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cm
 #endif
 }
 
-static int Mod_Q1BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3])
+void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, texture_t *boxtexture)
 {
-       double t1, t2;
-       double midf, mid[3];
-       int ret, side;
-
-       // check for empty
-       while (node->plane)
+       memset(trace, 0, sizeof(trace_t));
+       trace->fraction = 1;
+       trace->realfraction = 1;
+       if (BoxesOverlap(start, start, cmins, cmaxs))
        {
-               // 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
+               trace->startsupercontents |= boxsupercontents;
+               if (hitsupercontentsmask & boxsupercontents)
                {
-                       if (t2 >= 0)
-                       {
-                               node = node->children[0];
-                               continue;
-                       }
-                       side = 0;
+                       trace->startsolid = true;
+                       trace->allsolid = true;
                }
-
-               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;
+       trace_t trace;
+       model->TraceLine(model, 0, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK);
+       return trace.fraction == 1;
 }
 
 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)
@@ -1356,7 +1431,6 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                        tx->skinframerate = 1;
                        tx->skinframes[0] = skinframe;
                        tx->currentskinframe = tx->skinframes[0];
-                       tx->basematerialflags = 0;
                }
                tx->basematerialflags = MATERIALFLAG_WALL;
                if (i == loadmodel->num_textures - 1)
@@ -1486,23 +1560,20 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                        // LordHavoc: HL sky textures are entirely different than quake
                        if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == 256 && mtheight == 128)
                        {
-                               if (loadmodel->isworldmodel)
+                               data = loadimagepixelsbgra(tx->name, false, false);
+                               if (data && image_width == 256 && image_height == 128)
                                {
-                                       data = loadimagepixelsbgra(tx->name, false, false);
-                                       if (data && image_width == 256 && image_height == 128)
-                                       {
-                                               R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
-                                               Mem_Free(data);
-                                       }
-                                       else if (mtdata != NULL)
-                                               R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1);
+                                       R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
+                                       Mem_Free(data);
                                }
+                               else if (mtdata != NULL)
+                                       R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1);
                        }
                        else
                        {
-                               skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va("textures/%s/%s", mapname, tx->name), TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS, false);
+                               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_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS, false);
+                                       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)
                                {
                                        // did not find external texture, load it from the bsp or wad3
@@ -1519,13 +1590,13 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                                                {
                                                        tx->width = image_width;
                                                        tx->height = image_height;
-                                                       skinframe = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0), pixels, image_width, 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);
                                        }
                                        else if (mtdata) // texture included
-                                               skinframe = R_SkinFrame_LoadInternalQuake(tx->name, TEXF_MIPMAP | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0), false, r_fullbrights.integer, mtdata, tx->width, tx->height);
+                                               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);
                                }
                                // if skinframe is still NULL the "missing" texture will be used
                                if (skinframe)
@@ -1561,7 +1632,7 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
                        else if (!strncmp(tx->name, "sky", 3))
                                tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW;
                        else if (!strcmp(tx->name, "caulk"))
-                               tx->basematerialflags = MATERIALFLAG_NODRAW;
+                               tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW;
                        else if (tx->skinframes[0] && tx->skinframes[0]->fog)
                                tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW;
 
@@ -1772,7 +1843,6 @@ 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;
@@ -1814,9 +1884,7 @@ static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data)
                                                {
                                                        k = value[i];
                                                        value[i] = 0;
-                                                       strlcpy(wadname, "textures/", sizeof(wadname));
-                                                       strlcat(wadname, &value[j], sizeof(wadname));
-                                                       W_LoadTextureWadFile(wadname, false);
+                                                       W_LoadTextureWadFile(&value[j], false);
                                                        j = i+1;
                                                        if (!k)
                                                                break;
@@ -1833,8 +1901,9 @@ static void Mod_Q1BSP_LoadEntities(lump_t *l)
        loadmodel->brush.entities = NULL;
        if (!l->filelen)
                return;
-       loadmodel->brush.entities = (char *)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);
 }
@@ -1945,6 +2014,9 @@ static void Mod_Q1BSP_LoadEdges(lump_t *l)
                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);
+                       if(!loadmodel->brushq1.numvertexes)
+                               Host_Error("Mod_Q1BSP_LoadEdges: %s has edges but no vertexes, cannot fix\n", loadmodel->name);
+                               
                        out->v[0] = 0;
                        out->v[1] = 0;
                }
@@ -2290,7 +2362,8 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                {
                        int lindex = loadmodel->brushq1.surfedges[firstedge + i];
                        float s, t;
-                       if (lindex > 0)
+                       // note: the q1bsp format does not allow a 0 surfedge (it would have no negative counterpart)
+                       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);
@@ -2347,6 +2420,7 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                if (i == -1)
                {
                        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)
                        {
@@ -2354,6 +2428,7 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                                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)
                        surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + i;
@@ -2400,6 +2475,7 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
        // 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;
 
                for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++)
@@ -2407,10 +2483,14 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                        int i, iu, iv, lightmapx = 0, lightmapy = 0;
                        float u, v, ubase, vbase, uscale, vscale;
 
+                       if (!loadmodel->brushq1.lightmapupdateflags[surfacenum])
+                               continue;
+
                        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))
                        {
@@ -2446,26 +2526,19 @@ static void Mod_Q1BSP_LoadFaces(lump_t *l)
                                iv = (int) v;
                                (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
-                       int stainmapsize = 0;
+                       // allocate stainmaps for permanent marks on walls and clear white
                        unsigned char *stainsamples = NULL;
-                       for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++)
-                       {
-                               ssize = (surface->lightmapinfo->extents[0] >> 4) + 1;
-                               tsize = (surface->lightmapinfo->extents[1] >> 4) + 1;
-                               stainmapsize += ssize * tsize * 3;
-                       }
-                       // allocate and clear to white
                        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;
@@ -2617,7 +2690,7 @@ static void Mod_Q1BSP_LoadLeafs(lump_t *l)
 
                out->firstleafsurface = loadmodel->brush.data_leafsurfaces + (unsigned short)LittleShort(in->firstmarksurface);
                out->numleafsurfaces = (unsigned short)LittleShort(in->nummarksurfaces);
-               if (out->firstleafsurface < 0 || (unsigned short)LittleShort(in->firstmarksurface) + out->numleafsurfaces > loadmodel->brush.num_leafsurfaces)
+               if ((unsigned short)LittleShort(in->firstmarksurface) + out->numleafsurfaces > loadmodel->brush.num_leafsurfaces)
                {
                        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;
@@ -3068,7 +3141,7 @@ static void Mod_Q1BSP_FinalizePortals(void)
                p = pnext;
        }
        // now recalculate the node bounding boxes from the leafs
-       Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes);
+       Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode);
 }
 
 /*
@@ -3280,7 +3353,7 @@ static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node)
 static void Mod_Q1BSP_MakePortals(void)
 {
        portalchain = NULL;
-       Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes);
+       Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode);
        Mod_Q1BSP_FinalizePortals();
 }
 
@@ -3289,7 +3362,7 @@ static void Mod_Q1BSP_MakePortals(void)
 static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p)
 {
        mnode_t *node;
-       node = model->brush.data_nodes;
+       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];
        if (((mleaf_t *)node)->clusterindex >= 0)
@@ -3337,7 +3410,7 @@ static int Mod_Q1BSP_FatPVS(dp_model_t *model, const vec3_t org, vec_t radius, u
        }
        if (!merge)
                memset(pvsbuffer, 0, bytes);
-       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes);
+       Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode);
        return bytes;
 }
 
@@ -3380,7 +3453,7 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        dheader_t *header;
        dmodel_t *bm;
        mempool_t *mainmempool;
-       float dist, modelyawradius, modelradius, *vec;
+       float dist, modelyawradius, modelradius;
        msurface_t *surface;
        int numshadowmeshtriangles;
        hullinfo_t hullinfo;
@@ -3433,6 +3506,8 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 
        mod->soundfromcenter = true;
        mod->TraceBox = Mod_Q1BSP_TraceBox;
+       mod->TraceLine = Mod_Q1BSP_TraceLine;
+       mod->TracePoint = Mod_Q1BSP_TracePoint;
        mod->PointSuperContents = Mod_Q1BSP_PointSuperContents;
        mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight;
        mod->brush.SuperContentsFromNativeContents = Mod_Q1BSP_SuperContentsFromNativeContents;
@@ -3448,9 +3523,15 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
        mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize;
        mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf;
-
-       if (loadmodel->isworldmodel)
-               Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
+       mod->Draw = R_Q1BSP_Draw;
+       mod->DrawDepth = R_Q1BSP_DrawDepth;
+       mod->DrawDebug = R_Q1BSP_DrawDebug;
+       mod->GetLightInfo = R_Q1BSP_GetLightInfo;
+       mod->CompileShadowMap = R_Q1BSP_CompileShadowMap;
+       mod->DrawShadowMap = R_Q1BSP_DrawShadowMap;
+       mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+       mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+       mod->DrawLight = R_Q1BSP_DrawLight;
 
 // load into heap
 
@@ -3556,21 +3637,25 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                if (i > 0)
                {
                        char name[10];
-                       // LordHavoc: only register submodels if it is the world
-                       // (prevents external bsp models from replacing world submodels with
-                       //  their own)
-                       if (!loadmodel->isworldmodel)
-                               continue;
                        // duplicate the basic information
                        dpsnprintf(name, sizeof(name), "*%i", i);
-                       mod = Mod_FindName(name);
+                       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.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;
@@ -3590,76 +3675,52 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                mod->firstmodelsurface = bm->firstface;
                mod->nummodelsurfaces = bm->numfaces;
 
+               // set node/leaf parents for this submodel
+               Mod_Q1BSP_LoadNodes_RecursiveSetParent(mod->brush.data_nodes + mod->brushq1.hulls[0].firstclipnode, NULL);
+
                // make the model surface list (used by shadowing/lighting)
-               mod->surfacelist = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int);
-               for (j = 0;j < mod->nummodelsurfaces;j++)
-                       mod->surfacelist[j] = mod->firstmodelsurface + j;
+               mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int);
+               Mod_MakeSortedSurfaces(mod);
 
-               // this gets altered below if sky or water is used
-               mod->DrawSky = NULL;
-               mod->DrawAddWaterPlanes = NULL;
-               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;
-               if (i != 0)
-               {
-                       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;
-               }
                // 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);
-               VectorCopy(bm->mins, mod->yawmins);
-               VectorCopy(bm->maxs, mod->yawmaxs);
-               VectorCopy(bm->mins, mod->rotatedmins);
-               VectorCopy(bm->maxs, mod->rotatedmaxs);
+               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 or water is used
+               mod->DrawSky = NULL;
+               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)
                {
-                       modelyawradius = 0;
-                       modelradius = 0;
                        for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++)
-                       {
-                               // we only need to have a drawsky function if it is used(usually only on world model)
                                if (surface->texture->basematerialflags & MATERIALFLAG_SKY)
-                                       mod->DrawSky = R_Q1BSP_DrawSky;
+                                       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))
-                                       mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
-                               // calculate bounding shapes
-                               for (k = 0, vec = (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex);k < surface->num_vertices;k++, vec += 3)
-                               {
-                                       dist = vec[0]*vec[0]+vec[1]*vec[1];
-                                       if (modelyawradius < dist)
-                                               modelyawradius = dist;
-                                       dist += vec[2]*vec[2];
-                                       if (modelradius < dist)
-                                               modelradius = dist;
-                               }
-                       }
-                       modelyawradius = sqrt(modelyawradius);
-                       modelradius = sqrt(modelradius);
-                       mod->yawmins[0] = min(mod->yawmins[0], -modelyawradius);
-                       mod->yawmaxs[0] = min(mod->yawmaxs[0], -modelyawradius);
-                       mod->yawmins[1] = min(mod->yawmins[1],  modelyawradius);
-                       mod->yawmaxs[1] = min(mod->yawmaxs[1],  modelyawradius);
-                       mod->rotatedmins[0] = min(mod->rotatedmins[0], -modelradius);
-                       mod->rotatedmaxs[0] = min(mod->rotatedmaxs[0],  modelradius);
-                       mod->rotatedmins[1] = min(mod->rotatedmins[1], -modelradius);
-                       mod->rotatedmaxs[1] = min(mod->rotatedmaxs[1],  modelradius);
-                       mod->rotatedmins[2] = min(mod->rotatedmins[2], -modelradius);
-                       mod->rotatedmaxs[2] = min(mod->rotatedmaxs[2],  modelradius);
-                       mod->radius = modelradius;
-                       mod->radius2 = modelradius * modelradius;
+                                       break;
+                       if (j < mod->nummodelsurfaces)
+                               mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
 
                        // build lightstyle update chains
                        // (used to rapidly mark lightmapupdateflags on many surfaces
@@ -4109,9 +4170,6 @@ void static Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        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);
 
        mod_base = (unsigned char *)header;
 
@@ -4165,10 +4223,13 @@ static void Mod_Q3BSP_LoadEntities(lump_t *l)
        loadmodel->brushq3.num_lightgrid_cellsize[2] = 128;
        if (!l->filelen)
                return;
-       loadmodel->brush.entities = (char *)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
+       // VorteX: q3map2 FS-R generates tangentspace deluxemaps for q3bsp and sets 'deluxeMaps' key
+       loadmodel->brushq3.deluxemapping = false;
        if (data && COM_ParseToken_Simple(&data, false, false) && com_token[0] == '{')
        {
                while (1)
@@ -4194,6 +4255,19 @@ static void Mod_Q3BSP_LoadEntities(lump_t *l)
                                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);
                        }
+                       else if (!strcmp("deluxeMaps", key))
+                       {
+                               if (!strcmp(com_token, "1"))
+                               {
+                                       loadmodel->brushq3.deluxemapping = true;
+                                       loadmodel->brushq3.deluxemapping_modelspace = true;
+                               }
+                               else if (!strcmp(com_token, "2"))
+                               {
+                                       loadmodel->brushq3.deluxemapping = true;
+                                       loadmodel->brushq3.deluxemapping_modelspace = false;
+                               }
+                       }
                }
        }
 }
@@ -4313,7 +4387,7 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
 {
        q3dbrush_t *in;
        q3mbrush_t *out;
-       int i, j, n, c, count, maxplanes;
+       int i, j, n, c, count, maxplanes, q3surfaceflags;
        colplanef_t *planes;
 
        in = (q3dbrush_t *)(mod_base + l->fileofs);
@@ -4349,15 +4423,20 @@ static void Mod_Q3BSP_LoadBrushes(lump_t *l)
                                Mem_Free(planes);
                        planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes);
                }
+               q3surfaceflags = 0;
                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;
+                       q3surfaceflags |= planes[j].q3surfaceflags;
                }
                // make the colbrush from the planes
-               out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents);
+               out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, q3surfaceflags, out->texture, true);
+
+               // this whole loop can take a while (e.g. on redstarrepublic4)
+               CL_KeepaliveMessage(false);
        }
        if (planes)
                Mem_Free(planes);
@@ -4448,6 +4527,8 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
                if (*out < 0 || *out >= loadmodel->brushq3.num_vertices)
                {
                        Con_Printf("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices), setting to 0\n", *out, loadmodel->brushq3.num_vertices);
+                       if(!loadmodel->brushq3.num_vertices)
+                               Host_Error("Mod_Q1BSP_LoadTrianglles: %s has triangles but no vertexes, cannot fix\n", loadmodel->name);
                        *out = 0;
                }
        }
@@ -4477,22 +4558,45 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
        if (cls.state == ca_dedicated)
                return;
 
-       FS_StripExtension(loadmodel->name, mapname, sizeof(mapname));
-       inpixels[0] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, 0), false, false);
-       if(inpixels[0])
+       if(mod_q3bsp_nolightmaps.integer)
        {
-               // 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);
-               }
-
-               size = image_width;
-               bytesperpixel = 4;
-               rgbmap[0] = 0;
-               rgbmap[1] = 1;
-               rgbmap[2] = 2;
+               return;
+       }
+       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;
+
+               // 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);
+               }
+
+               size = image_width;
+               bytesperpixel = 4;
+               rgbmap[0] = 0;
+               rgbmap[1] = 1;
+               rgbmap[2] = 2;
                external = true;
 
                for(count = 1; ; ++count)
@@ -4508,23 +4612,10 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
                        }
                }
        }
-       else
-       {
-               if (!l->filelen)
-               {
-                       return;
-               }
-               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);
-               loadmodel->brushq3.num_originallightmaps = count;
-               for(i = 0; i < count; ++i)
-                       inpixels[i] = input_pointer[i].rgb;
-       }
 
-       convertedpixels = Mem_Alloc(tempmempool, size*size*4); // TODO free this
+       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
@@ -4535,71 +4626,75 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
        // 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)
+       // VorteX: autodetect only if previous attempt to find "deluxeMaps" key
+       // in Mod_Q3BSP_LoadEntities was failed
+       if (!loadmodel->brushq3.deluxemapping)
+       {
+               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++)
                        {
-                               endlightmap = max(endlightmap, j + 1);
-                               if ((j & 1) || j + 1 >= count)
+                               j = LittleLong(faces[i].lightmapindex);
+                               if (j >= 0)
                                {
-                                       loadmodel->brushq3.deluxemapping = false;
-                                       break;
+                                       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)
+               // 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)
                {
-                       // all pixels in the unused lightmap were black...
-                       loadmodel->brushq3.deluxemapping = false;
+                       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 (power = 1;power <= mod_q3bsp_lightmapmergepower.integer && (128 << power) <= gl_max_texture_size && (1 << (power * 2)) < 4 * (count >> loadmodel->brushq3.deluxemapping);power++)
-               loadmodel->brushq3.num_lightmapmergepower = power;
 
-       // as the lightmap size may actually be another power of 2, adjust for this
-       // (and interpret it as the power for 128x128 lightmaps above)
        for(i = 0; (128 << i) < size; ++i)
-               loadmodel->brushq3.num_lightmapmergepower -= 1;
-       if(loadmodel->brushq3.num_lightmapmergepower < 0)
-               loadmodel->brushq3.num_lightmapmergepower = 0;
+               ;
+       // 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 ? 1 : 0)); 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.num_mergedlightmaps = ((count >> (loadmodel->brushq3.deluxemapping ? 1 : 0)) + (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 *));
@@ -4633,7 +4728,7 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
                                // 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);
+                               j = (count >> (loadmodel->brushq3.deluxemapping ? 1 : 0)) - (lightmapindex << power2);
                                for (mergewidth = 1;mergewidth < j && mergewidth < (1 << power);mergewidth *= 2)
                                        ;
                                for (mergeheight = 1;mergewidth*mergeheight < j && mergeheight < (1 << power);mergeheight *= 2)
@@ -4646,7 +4741,7 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
                        }
                        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);
+                       j = (i >> (loadmodel->brushq3.deluxemapping ? 1 : 0)) & ((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
@@ -4662,6 +4757,7 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
                }
        }
 
+       Mem_Free(convertedpixels);
        if(external)
        {
                for(i = 0; i < count; ++i)
@@ -4669,11 +4765,81 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
        }
 }
 
+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, *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;
+       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;
@@ -4685,6 +4851,9 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
        float *originaltexcoordtexture2f;
        float *originaltexcoordlightmap2f;
        float *v;
+       patchtess_t *patchtess = NULL;
+       int patchtesscount = 0;
+       qboolean again;
 
        in = (q3dface_t *)(mod_base + l->fileofs);
        if (l->filelen % sizeof(*in))
@@ -4695,6 +4864,9 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
        loadmodel->data_surfaces = out;
        loadmodel->num_surfaces = count;
 
+       if(count > 0)
+               patchtess = (patchtess_t*) Mem_Alloc(tempmempool, count * sizeof(*patchtess));
+
        i = 0;
        oldi = i;
        oldin = in;
@@ -4742,7 +4914,8 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                n = -1;
                        else if (n >= loadmodel->brushq3.num_originallightmaps)
                        {
-                               Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, 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
@@ -4787,6 +4960,7 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                                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);
@@ -4794,20 +4968,37 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        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(1, xtess, 1024);
-                       ytess = bound(1, ytess, 1024);
-                       // bound to user limit on vertices
-                       while ((xtess > 1 || ytess > 1) && (((patchsize[0] - 1) * xtess) + 1) * (((patchsize[1] - 1) * ytess) + 1) > min(r_subdivisions_maxvertices.integer, 262144))
-                       {
-                               if (xtess > ytess)
-                                       xtess--;
-                               else
-                                       ytess--;
-                       }
-                       finalwidth = ((patchsize[0] - 1) * xtess) + 1;
-                       finalheight = ((patchsize[1] - 1) * ytess) + 1;
-                       numvertices = finalwidth * finalheight;
-                       numtriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+                       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)
@@ -4821,6 +5012,38 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                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;
@@ -4870,25 +5093,25 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + firstvertex * 2;
                        originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + firstvertex * 2;
                        originalcolor4f = loadmodel->brushq3.data_color4f + firstvertex * 4;
-                       // 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(1, xtess, 1024);
-                       ytess = bound(1, ytess, 1024);
-                       // bound to user limit on vertices
-                       while ((xtess > 1 || ytess > 1) && (((patchsize[0] - 1) * xtess) + 1) * (((patchsize[1] - 1) * ytess) + 1) > min(r_subdivisions_maxvertices.integer, 262144))
+
+                       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)
                        {
-                               if (xtess > ytess)
-                                       xtess--;
-                               else
-                                       ytess--;
+                               Con_Printf("ERROR: patch %d isn't preprocessed?!?\n", i);
+                               xtess = ytess = cxtess = cytess = 0;
                        }
-                       finalwidth = ((patchsize[0] - 1) * xtess) + 1;
-                       finalheight = ((patchsize[1] - 1) * ytess) + 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;
                        type = Q3FACETYPE_MESH;
@@ -4900,7 +5123,9 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        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)
@@ -4910,24 +5135,8 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        }
                        // q3map does not put in collision brushes for curves... ugh
                        // build the lower quality collision geometry
-                       xtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value);
-                       ytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value);
-                       // bound to user settings
-                       xtess = bound(r_subdivisions_collision_mintess.integer, xtess, r_subdivisions_collision_maxtess.integer);
-                       ytess = bound(r_subdivisions_collision_mintess.integer, ytess, r_subdivisions_collision_maxtess.integer);
-                       // bound to sanity settings
-                       xtess = bound(1, xtess, 1024);
-                       ytess = bound(1, ytess, 1024);
-                       // bound to user limit on vertices
-                       while ((xtess > 1 || ytess > 1) && (((patchsize[0] - 1) * xtess) + 1) * (((patchsize[1] - 1) * ytess) + 1) > min(r_subdivisions_collision_maxvertices.integer, 262144))
-                       {
-                               if (xtess > ytess)
-                                       xtess--;
-                               else
-                                       ytess--;
-                       }
-                       finalwidth = ((patchsize[0] - 1) * xtess) + 1;
-                       finalheight = ((patchsize[1] - 1) * ytess) + 1;
+                       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;
 
@@ -4935,7 +5144,7 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        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, xtess, ytess);
+                       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);
@@ -4944,6 +5153,11 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        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;
@@ -4974,7 +5188,7 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        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 lightmapindex = LittleLong(in->lightmapindex) >> (loadmodel->brushq3.deluxemapping ? 1 : 0);
                                int mergewidth = R_TextureWidth(out->lightmaptexture) / loadmodel->brushq3.lightmapsize;
                                int mergeheight = R_TextureHeight(out->lightmaptexture) / loadmodel->brushq3.lightmapsize;
                                lightmapindex &= mergewidth * mergeheight - 1;
@@ -5014,6 +5228,18 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                //out->lightmapinfo->styles[3] = 255;
        }
 
+       i = oldi;
+       out = oldout;
+       for (;i < count;i++, out++)
+       {
+               if(out->num_vertices && out->num_triangles)
+                       continue;
+               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);
 
@@ -5036,6 +5262,9 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
        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)
@@ -5245,7 +5474,7 @@ static void Mod_Q3BSP_LoadLightGrid(lump_t *l)
        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_dimensions[0], loadmodel->brushq3.num_lightgrid_dimensions[1], loadmodel->brushq3.num_lightgrid_dimensions[2]);
+                       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));
@@ -5359,6 +5588,84 @@ static void Mod_Q3BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambie
        //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 int Mod_Q3BSP_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_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side    ], p1, mid);
+               if (ret != 0)
+                       return ret;
+               ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2);
+               if (ret != 1)
+                       return ret;
+               return 2;
+       }
+       return ((mleaf_t *)node)->clusterindex < 0;
+}
+
+static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end)
+{
+       if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer)
+       {
+               trace_t trace;
+               model->TraceLine(model, 0, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK);
+               return trace.fraction == 1;
+       }
+       else
+       {
+               double tracestart[3], traceend[3];
+               VectorCopy(start, tracestart);
+               VectorCopy(end, traceend);
+               return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend);
+       }
+}
+
 static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe)
 {
        int i;
@@ -5462,7 +5769,7 @@ static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *mod
                        if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
                        {
                                surface->collisionmarkframe = markframe;
-                               Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
+                               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);
                        }
                }
        }
@@ -5543,90 +5850,122 @@ static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *mo
                        if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
                        {
                                surface->collisionmarkframe = markframe;
-                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
+                               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(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)
+static int markframe = 0;
+
+static void Mod_Q3BSP_TracePoint(dp_model_t *model, int frame, trace_t *trace, const vec3_t start, int hitsupercontentsmask)
+{
+       int i;
+       q3mbrush_t *brush;
+       memset(trace, 0, sizeof(*trace));
+       trace->fraction = 1;
+       trace->realfraction = 1;
+       trace->hitsupercontentsmask = hitsupercontentsmask;
+       if (model->brush.submodel)
+       {
+               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                       if (brush->colbrushf)
+                               Collision_TracePointBrushFloat(trace, start, brush->colbrushf);
+       }
+       else
+               Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, ++markframe);
+}
+
+static void Mod_Q3BSP_TraceLine(dp_model_t *model, int frame, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask)
 {
        int i;
        float segmentmins[3], segmentmaxs[3];
-       static int markframe = 0;
        msurface_t *surface;
        q3mbrush_t *brush;
+
+       if (VectorCompare(start, end))
+       {
+               Mod_Q3BSP_TracePoint(model, frame, trace, start, hitsupercontentsmask);
+               return;
+       }
+
        memset(trace, 0, sizeof(*trace));
        trace->fraction = 1;
        trace->realfraction = 1;
        trace->hitsupercontentsmask = hitsupercontentsmask;
-       if (mod_q3bsp_optimizedtraceline.integer && VectorLength2(boxmins) + VectorLength2(boxmaxs) == 0)
+       segmentmins[0] = min(start[0], end[0]) - 1;
+       segmentmins[1] = min(start[1], end[1]) - 1;
+       segmentmins[2] = min(start[2], end[2]) - 1;
+       segmentmaxs[0] = max(start[0], end[0]) + 1;
+       segmentmaxs[1] = max(start[1], end[1]) + 1;
+       segmentmaxs[2] = max(start[2], end[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, start, end, 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, start, 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_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, 0, 1, start, end, ++markframe, segmentmins, segmentmaxs);
+}
+
+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;
+       float segmentmins[3], segmentmaxs[3];
+       msurface_t *surface;
+       q3mbrush_t *brush;
+       colboxbrushf_t thisbrush_start, thisbrush_end;
+       vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs;
+
+       if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(boxmins, boxmaxs))
        {
+               vec3_t shiftstart, shiftend;
+               VectorAdd(start, boxmins, shiftstart);
+               VectorAdd(end, boxmins, shiftend);
                if (VectorCompare(start, end))
-               {
-                       // point trace
-                       if (model->brush.submodel)
-                       {
-                               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
-                                       if (brush->colbrushf)
-                                               Collision_TracePointBrushFloat(trace, start, brush->colbrushf);
-                       }
-                       else
-                               Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, ++markframe);
-               }
+                       Mod_Q3BSP_TracePoint(model, frame, trace, shiftstart, hitsupercontentsmask);
                else
                {
-                       // line trace
-                       segmentmins[0] = min(start[0], end[0]) - 1;
-                       segmentmins[1] = min(start[1], end[1]) - 1;
-                       segmentmins[2] = min(start[2], end[2]) - 1;
-                       segmentmaxs[0] = max(start[0], end[0]) + 1;
-                       segmentmaxs[1] = max(start[1], end[1]) + 1;
-                       segmentmaxs[2] = max(start[2], end[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, start, end, 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, start, end, surface->num_collisiontriangles, surface->data_collisionelement3i, surface->data_collisionvertex3f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
-                       }
-                       else
-                               Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, 0, 1, start, end, ++markframe, segmentmins, segmentmaxs);
+                       Mod_Q3BSP_TraceLine(model, frame, trace, shiftstart, shiftend, hitsupercontentsmask);
+                       VectorSubtract(trace->endpos, boxmins, trace->endpos);
                }
+               return;
        }
-       else
+
+       // box trace, performed as brush trace
+       memset(trace, 0, sizeof(*trace));
+       trace->fraction = 1;
+       trace->realfraction = 1;
+       trace->hitsupercontentsmask = hitsupercontentsmask;
+       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);
+       Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL);
+       Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL);
+       if (model->brush.submodel)
        {
-               // box trace, performed as brush trace
-               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, 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, 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->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
-               }
-               else
-                       Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, model->brush.data_nodes, thisbrush_start, thisbrush_end, ++markframe, segmentmins, segmentmaxs);
+               for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+                       if (brush->colbrushf)
+                               Collision_TraceBrushBrushFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, 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_TraceBrushTriangleMeshFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, 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, model->brush.data_nodes, &thisbrush_start.brush, &thisbrush_end.brush, ++markframe, segmentmins, segmentmaxs);
 }
 
 static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point)
@@ -5684,6 +6023,10 @@ static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nati
                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;
 }
 
@@ -5710,6 +6053,10 @@ static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supe
                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;
 }
 
@@ -5740,18 +6087,19 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        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 && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE)
                Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION);
-       mod->brush.ishlbsp = false;
-       if (loadmodel->isworldmodel)
-               Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
 
        mod->soundfromcenter = true;
        mod->TraceBox = Mod_Q3BSP_TraceBox;
+       mod->TraceLine = Mod_Q3BSP_TraceLine;
+       mod->TracePoint = Mod_Q3BSP_TracePoint;
        mod->PointSuperContents = Mod_Q3BSP_PointSuperContents;
-       mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight;
+       mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight;
        mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents;
        mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents;
        mod->brush.GetPVS = Mod_Q1BSP_GetPVS;
@@ -5762,15 +6110,18 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters;
        mod->brush.LightPoint = Mod_Q3BSP_LightPoint;
        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->CompileShadowMap = R_Q1BSP_CompileShadowMap;
+       mod->DrawShadowMap = R_Q1BSP_DrawShadowMap;
        mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
        mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
        mod->DrawLight = R_Q1BSP_DrawLight;
-       mod->DrawAddWaterPlanes = NULL;
 
        mod_base = (unsigned char *)header;
 
@@ -5780,14 +6131,25 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        lumps = (header->version == Q3BSPVERSION_LIVE) ? Q3HEADER_LUMPS_LIVE : Q3HEADER_LUMPS;
        for (i = 0;i < lumps;i++)
        {
-               header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs);
-               header->lumps[i].filelen = LittleLong(header->lumps[i].filelen);
-       }
+               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;
@@ -5799,6 +6161,9 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                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]);
@@ -5831,17 +6196,20 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 
        // 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++)
+       if (cls.state != ca_dedicated)
        {
-               surface->num_firstshadowmeshtriangle = numshadowmeshtriangles;
-               numshadowmeshtriangles += surface->num_triangles;
+               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.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);
@@ -5852,20 +6220,17 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                if (i > 0)
                {
                        char name[10];
-                       // LordHavoc: only register submodels if it is the world
-                       // (prevents external bsp models from replacing world submodels with
-                       //  their own)
-                       if (!loadmodel->isworldmodel)
-                               continue;
                        // duplicate the basic information
                        dpsnprintf(name, sizeof(name), "*%i", i);
-                       mod = Mod_FindName(name);
+                       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;
@@ -5873,7 +6238,7 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                        mod->brush.BoxTouchingVisibleLeafs = NULL;
                        mod->brush.FindBoxClusters = NULL;
                        mod->brush.LightPoint = NULL;
-                       mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
+                       mod->brush.AmbientSoundLevelsForPoint = NULL;
                }
                mod->brush.submodel = i;
 
@@ -5882,9 +6247,8 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                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->surfacelist = (int *)Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->surfacelist));
-               for (j = 0;j < mod->nummodelsurfaces;j++)
-                       mod->surfacelist[j] = mod->firstmodelsurface + j;
+               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);
@@ -5926,21 +6290,21 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                mod->radius = modelradius;
                mod->radius2 = modelradius * modelradius;
 
+               // 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->nummodelsurfaces)
                        mod->DrawSky = R_Q1BSP_DrawSky;
-               else
-                       mod->DrawSky = NULL;
 
                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;
-               else
-                       mod->DrawAddWaterPlanes = NULL;
        }
 }
 
@@ -5960,6 +6324,467 @@ void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        Host_Error("Mod_MAP_Load: not yet implemented");
 }
 
+typedef struct objvertex_s
+{
+       float v[3];
+       float vt[2];
+       float vn[3];
+}
+objvertex_t;
+
+typedef struct objtriangle_s
+{
+       objvertex_t vertex[3];
+       int textureindex;
+}
+objtriangle_t;
+
+void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend)
+{
+#if 0
+       const char *textbase = (char *)buffer, *text = textbase;
+       char *s;
+       char *argv[512];
+       char line[1024];
+       char materialname[MAX_QPATH];
+       int j, index1, index2, index3, first, prev, index;
+       int argc;
+       int linelen;
+       int numtriangles = 0;
+       int maxtriangles = 131072;
+       objtriangle_t *triangles = Mem_Alloc(tempmempool, maxtriangles * sizeof(*triangles));
+       int linenumber = 0;
+       int maxtextures = 256, numtextures = 0, textureindex = 0;
+       int maxv = 1024, numv = 0;
+       int maxvt = 1024, numvt = 0;
+       int maxvn = 1024, numvn = 0;
+       char **texturenames;
+       float *v = Mem_Alloc(tempmempool, maxv * sizeof(float[3]));
+       float *vt = Mem_Alloc(tempmempool, maxvt * sizeof(float[2]));
+       float *vn = Mem_Alloc(tempmempool, maxvn * sizeof(float[3]));
+       objvertex_t vfirst, vprev, vcurrent;
+#if 0
+       int hashindex;
+       int maxverthash = 65536, numverthash = 0;
+       int numhashindex = 65536;
+       struct objverthash_s
+       {
+               struct objverthash_s *next;
+               int s;
+               int v;
+               int vt;
+               int vn;
+       }
+       *hash, **verthash = Mem_Alloc(tempmempool, numhashindex * sizeof(*verthash)), *verthashdata = Mem_Alloc(tempmempool, maxverthash * sizeof(*verthashdata)), *oldverthashdata;
+#endif
+
+       dpsnprintf(materialname, sizeof(materialname), "%s", loadmodel->name);
+
+       loadmodel->modeldatatypestring = "OBJ";
+
+       loadmodel->type = mod_obj;
+       loadmodel->soundfromcenter = true;
+       loadmodel->TraceBox = Mod_OBJ_TraceBox;
+       loadmodel->TraceLine = Mod_OBJ_TraceLine;
+       loadmodel->TracePoint = Mod_OBJ_TracePoint;
+       loadmodel->PointSuperContents = Mod_OBJ_PointSuperContents;
+       loadmodel->brush.TraceLineOfSight = Mod_OBJ_TraceLineOfSight;
+       loadmodel->brush.SuperContentsFromNativeContents = Mod_OBJ_SuperContentsFromNativeContents;
+       loadmodel->brush.NativeContentsFromSuperContents = Mod_OBJ_NativeContentsFromSuperContents;
+       loadmodel->brush.GetPVS = Mod_OBJ_GetPVS;
+       loadmodel->brush.FatPVS = Mod_OBJ_FatPVS;
+       loadmodel->brush.BoxTouchingPVS = Mod_OBJ_BoxTouchingPVS;
+       loadmodel->brush.BoxTouchingLeafPVS = Mod_OBJ_BoxTouchingLeafPVS;
+       loadmodel->brush.BoxTouchingVisibleLeafs = Mod_OBJ_BoxTouchingVisibleLeafs;
+       loadmodel->brush.FindBoxClusters = Mod_OBJ_FindBoxClusters;
+       loadmodel->brush.LightPoint = Mod_OBJ_LightPoint;
+       loadmodel->brush.FindNonSolidLocation = Mod_OBJ_FindNonSolidLocation;
+       loadmodel->brush.AmbientSoundLevelsForPoint = NULL;
+       loadmodel->brush.RoundUpToHullSize = NULL;
+       loadmodel->brush.PointInLeaf = Mod_OBJ_PointInLeaf;
+       loadmodel->Draw = R_Q1BSP_Draw;
+       loadmodel->DrawDepth = R_Q1BSP_DrawDepth;
+       loadmodel->DrawDebug = R_Q1BSP_DrawDebug;
+       loadmodel->GetLightInfo = R_Q1BSP_GetLightInfo;
+       loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap;
+       loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap;
+       loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+       loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+       loadmodel->DrawLight = R_Q1BSP_DrawLight;
+
+       // parse the OBJ text now
+       for(;;)
+       {
+               if (!*text)
+                       break;
+               linenumber++;
+               linelen = 0;
+               for (linelen = 0;text[linelen] && text[linelen] != '\r' && text[linelen] != '\n';linelen++)
+                       line[linelen] = text[linelen];
+               line[linelen] = 0;
+               for (argc = 0;argc < (int)(sizeof(argv)/sizeof(argv[0]));argc++)
+                       argv[argc] = "";
+               argc = 0;
+               s = line;
+               while (*s == ' ' || *s == '\t')
+                       s++;
+               while (*s)
+               {
+                       argv[argc++] = s;
+                       while (*s > ' ')
+                               s++;
+                       if (!*s)
+                               break;
+                       *s++ = 0;
+                       while (*s == ' ' || *s == '\t')
+                               s++;
+               }
+               if (!argc)
+                       continue;
+               if (argv[0][0] == '#')
+                       continue;
+               if (!strcmp(argv[0], "v"))
+               {
+                       if (maxv <= numv)
+                       {
+                               float *oldv = v;
+                               maxv *= 2;
+                               v = Mem_Alloc(tempmempool, maxv * sizeof(float[3]));
+                               if (oldv)
+                               {
+                                       memcpy(v, oldv, numv * sizeof(float[3]));
+                                       Mem_Free(oldv);
+                               }
+                       }
+                       v[numv*3+0] = atof(argv[1]);
+                       v[numv*3+1] = atof(argv[2]);
+                       v[numv*3+2] = atof(argv[3]);
+                       numv++;
+               }
+               else if (!strcmp(argv[0], "vt"))
+               {
+                       if (maxvt <= numvt)
+                       {
+                               float *oldvt = vt;
+                               maxvt *= 2;
+                               vt = Mem_Alloc(tempmempool, maxvt * sizeof(float[2]));
+                               if (oldvt)
+                               {
+                                       memcpy(vt, oldvt, numvt * sizeof(float[2]));
+                                       Mem_Free(oldvt);
+                               }
+                       }
+                       vt[numvt*2+0] = atof(argv[1]);
+                       vt[numvt*2+1] = atof(argv[2]);
+                       numvt++;
+               }
+               else if (!strcmp(argv[0], "vn"))
+               {
+                       if (maxvn <= numvn)
+                       {
+                               float *oldvn = vn;
+                               maxvn *= 2;
+                               vn = Mem_Alloc(tempmempool, maxvn * sizeof(float[3]));
+                               if (oldvn)
+                               {
+                                       memcpy(vn, oldvn, numvn * sizeof(float[3]));
+                                       Mem_Free(oldvn);
+                               }
+                       }
+                       vn[numvn*3+0] = atof(argv[1]);
+                       vn[numvn*3+1] = atof(argv[2]);
+                       vn[numvn*3+2] = atof(argv[3]);
+                       numvn++;
+               }
+               else if (!strcmp(argv[0], "f"))
+               {
+                       for (j = 1;j < argc;j++)
+                       {
+                               index1 = atoi(argv[j]);
+                               while(argv[j][0] && argv[j][0] != '/')
+                                       argv[j]++;
+                               if (argv[j][0])
+                                       argv[j]++;
+                               index2 = atoi(argv[j]);
+                               while(argv[j][0] && argv[j][0] != '/')
+                                       argv[j]++;
+                               if (argv[j][0])
+                                       argv[j]++;
+                               index3 = atoi(argv[j]);
+                               // negative refers to a recent vertex
+                               // zero means not specified
+                               // positive means an absolute vertex index
+                               if (index1 < 0)
+                                       index1 = numv - index1;
+                               if (index2 < 0)
+                                       index2 = numvt - index2;
+                               if (index3 < 0)
+                                       index3 = numvn - index3;
+                               VectorCopy(v + 3*index1, vcurrent.v);
+                               Vector2Copy(vt + 2*index2, vcurrent.vt);
+                               VectorCopy(vn + 3*index3, vcurrent.vn);
+                               if (j == 1)
+                                       vfirst = vcurrent;
+                               else if (j >= 3)
+                               {
+                                       if (maxtriangles <= numtriangles)
+                                       {
+                                               objtriangle_t *oldtriangles = triangles;
+                                               maxtriangles *= 2;
+                                               triangles = Mem_Alloc(tempmempool, numtriangles * sizeof(*triangles));
+                                               if (oldtriangles)
+                                               {
+                                                       memcpy(triangles, oldtriangles, numtriangles * sizeof(*triangles));
+                                                       Mem_Free(oldtriangles);
+                                               }
+                                       }
+                                       triangles[numtriangles].textureindex = textureindex;
+                                       triangles[numtriangles].vertex[0] = vfirst;
+                                       triangles[numtriangles].vertex[1] = vprev;
+                                       triangles[numtriangles].vertex[2] = vcurrent;
+                                       numtriangles++;
+                               }
+                               vprev = vcurrent;
+                               prev = index;
+                       }
+               }
+               else if (!strcmp(argv[0], "o") || !strcmp(argv[0], "g"))
+                       ;
+               else if (!!strcmp(argv[0], "usemtl"))
+               {
+                       for (i = 0;i < numtextures;i++)
+                               if (!strcmp(texturenames[numtextures], argv[1]))
+                                       break;
+                       if (i < numtextures)
+                               texture = textures + i;
+                       else
+                       {
+                               if (maxtextures <= numtextures)
+                               {
+                                       texture_t *oldtextures = textures;
+                                       maxtextures *= 2;
+                                       textures = Mem_Alloc(tempmempool, maxtextures * sizeof(*textures));
+                                       if (oldtextures)
+                                       {
+                                               memcpy(textures, oldtextures, numtexutres * sizeof(*textures));
+                                               Mem_Free(oldtextures);
+                                       }
+                               }
+                               textureindex = numtextures++;
+                               texturenames[textureindex] = Mem_Alloc(tempmempool, strlen(argv[1]) + 1);
+                               memcpy(texturenames[textureindex], argv[1], strlen(argv[1]) + 1);
+                       }
+               }
+               text += linelen;
+               if (*text == '\r')
+                       text++;
+               if (*text == '\n')
+                       text++;
+       }
+
+       // now that we have the OBJ data loaded as-is, we can convert it
+
+       // load the textures
+       loadmodel->num_textures = numtextures;
+       loadmodel->data_textures = Mem_Alloc(loadmodel->mempool, loadmodel->num_textures * sizeof(texture_t));
+       for (i = 0;i < numtextures;i++)
+               Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i, texturenames[i], true, true, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PRECACHE | (r_picmipworld.integer ? TEXF_PICMIP : 0) | TEXF_COMPRESS);
+
+       // free the texturenames array since we are now done with it
+       for (i = 0;i < numtextures;i++)
+       {
+               Mem_Free(texturenames[i]);
+               texturenames[i] = NULL;
+       }
+       Mem_Free(texturenames);
+       texturenames = NULL;
+
+       // generate a rough BSP tree from triangle data, we don't have to be too careful here, it only has to define the basic areas of the map
+
+       // generate surfaces by recursing triangles into BSP tree and ensuring they do not overlap in the lightmap projection axis
+
+       loadmodel->numskins = LittleLong(pinmodel->num_skins);
+       numxyz = LittleLong(pinmodel->num_xyz);
+       numst = LittleLong(pinmodel->num_st);
+       loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->num_tris);
+       loadmodel->numframes = LittleLong(pinmodel->num_frames);
+       loadmodel->surfmesh.num_morphframes = loadmodel->numframes;
+       loadmodel->num_poses = loadmodel->surfmesh.num_morphframes;
+       skinwidth = LittleLong(pinmodel->skinwidth);
+       skinheight = LittleLong(pinmodel->skinheight);
+       iskinwidth = 1.0f / skinwidth;
+       iskinheight = 1.0f / skinheight;
+
+       loadmodel->num_surfaces = 1;
+       loadmodel->nummodelsurfaces = loadmodel->num_surfaces;
+       data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->numframes * sizeof(animscene_t) + loadmodel->numframes * sizeof(float[6]) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + loadmodel->surfmesh.num_triangles * sizeof(int[3]));
+       loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t);
+       loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int);
+       loadmodel->sortedmodelsurfaces[0] = 0;
+       loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t);
+       loadmodel->surfmesh.data_morphmd2framesize6f = (float *)data;data += loadmodel->numframes * sizeof(float[6]);
+       loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]);
+       loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]);
+
+       loadmodel->synctype = ST_RAND;
+
+       // load the skins
+       inskin = (char *)(base + LittleLong(pinmodel->ofs_skins));
+       skinfiles = Mod_LoadSkinFiles();
+       if (skinfiles)
+       {
+               loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins;
+               loadmodel->num_texturesperskin = loadmodel->num_surfaces;
+               loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t));
+               Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", "");
+               Mod_FreeSkinFiles(skinfiles);
+       }
+       else if (loadmodel->numskins)
+       {
+               // skins found (most likely not a player model)
+               loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins;
+               loadmodel->num_texturesperskin = loadmodel->num_surfaces;
+               loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t));
+               for (i = 0;i < loadmodel->numskins;i++, inskin += MD2_SKINNAME)
+                       Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i * loadmodel->num_surfaces, inskin, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PRECACHE | TEXF_PICMIP | TEXF_COMPRESS);
+       }
+       else
+       {
+               // no skins (most likely a player model)
+               loadmodel->numskins = 1;
+               loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins;
+               loadmodel->num_texturesperskin = loadmodel->num_surfaces;
+               loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t));
+               Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures, NULL);
+       }
+
+       loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins);
+       for (i = 0;i < loadmodel->numskins;i++)
+       {
+               loadmodel->skinscenes[i].firstframe = i;
+               loadmodel->skinscenes[i].framecount = 1;
+               loadmodel->skinscenes[i].loop = true;
+               loadmodel->skinscenes[i].framerate = 10;
+       }
+
+       // load the triangles and stvert data
+       inst = (unsigned short *)(base + LittleLong(pinmodel->ofs_st));
+       intri = (md2triangle_t *)(base + LittleLong(pinmodel->ofs_tris));
+       md2verthash = (struct md2verthash_s **)Mem_Alloc(tempmempool, 65536 * sizeof(hash));
+       md2verthashdata = (struct md2verthash_s *)Mem_Alloc(tempmempool, loadmodel->surfmesh.num_triangles * 3 * sizeof(*hash));
+       // swap the triangle list
+       loadmodel->surfmesh.num_vertices = 0;
+       for (i = 0;i < loadmodel->surfmesh.num_triangles;i++)
+       {
+               for (j = 0;j < 3;j++)
+               {
+                       xyz = (unsigned short) LittleShort (intri[i].index_xyz[j]);
+                       st = (unsigned short) LittleShort (intri[i].index_st[j]);
+                       if (xyz >= numxyz)
+                       {
+                               Con_Printf("%s has an invalid xyz index (%i) on triangle %i, resetting to 0\n", loadmodel->name, xyz, i);
+                               xyz = 0;
+                       }
+                       if (st >= numst)
+                       {
+                               Con_Printf("%s has an invalid st index (%i) on triangle %i, resetting to 0\n", loadmodel->name, st, i);
+                               st = 0;
+                       }
+                       hashindex = (xyz * 256 + st) & 65535;
+                       for (hash = md2verthash[hashindex];hash;hash = hash->next)
+                               if (hash->xyz == xyz && hash->st == st)
+                                       break;
+                       if (hash == NULL)
+                       {
+                               hash = md2verthashdata + loadmodel->surfmesh.num_vertices++;
+                               hash->xyz = xyz;
+                               hash->st = st;
+                               hash->next = md2verthash[hashindex];
+                               md2verthash[hashindex] = hash;
+                       }
+                       loadmodel->surfmesh.data_element3i[i*3+j] = (hash - md2verthashdata);
+               }
+       }
+
+       vertremap = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(int));
+       data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t));
+       loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]);
+       loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)data;data += loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t);
+       for (i = 0;i < loadmodel->surfmesh.num_vertices;i++)
+       {
+               int sts, stt;
+               hash = md2verthashdata + i;
+               vertremap[i] = hash->xyz;
+               sts = LittleShort(inst[hash->st*2+0]);
+               stt = LittleShort(inst[hash->st*2+1]);
+               if (sts < 0 || sts >= skinwidth || stt < 0 || stt >= skinheight)
+               {
+                       Con_Printf("%s has an invalid skin coordinate (%i %i) on vert %i, changing to 0 0\n", loadmodel->name, sts, stt, i);
+                       sts = 0;
+                       stt = 0;
+               }
+               loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = sts * iskinwidth;
+               loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = stt * iskinheight;
+       }
+
+       Mem_Free(md2verthash);
+       Mem_Free(md2verthashdata);
+
+       // generate ushort elements array if possible
+       if (loadmodel->surfmesh.num_vertices <= 65536)
+               loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles);
+
+       // load the frames
+       datapointer = (base + LittleLong(pinmodel->ofs_frames));
+       for (i = 0;i < loadmodel->surfmesh.num_morphframes;i++)
+       {
+               int k;
+               trivertx_t *v;
+               trivertx_t *out;
+               pinframe = (md2frame_t *)datapointer;
+               datapointer += sizeof(md2frame_t);
+               // store the frame scale/translate into the appropriate array
+               for (j = 0;j < 3;j++)
+               {
+                       loadmodel->surfmesh.data_morphmd2framesize6f[i*6+j] = LittleFloat(pinframe->scale[j]);
+                       loadmodel->surfmesh.data_morphmd2framesize6f[i*6+3+j] = LittleFloat(pinframe->translate[j]);
+               }
+               // convert the vertices
+               v = (trivertx_t *)datapointer;
+               out = loadmodel->surfmesh.data_morphmdlvertex + i * loadmodel->surfmesh.num_vertices;
+               for (k = 0;k < loadmodel->surfmesh.num_vertices;k++)
+                       out[k] = v[vertremap[k]];
+               datapointer += numxyz * sizeof(trivertx_t);
+
+               strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name));
+               loadmodel->animscenes[i].firstframe = i;
+               loadmodel->animscenes[i].framecount = 1;
+               loadmodel->animscenes[i].framerate = 10;
+               loadmodel->animscenes[i].loop = true;
+       }
+
+       Mem_Free(vertremap);
+
+       Mod_MakeSortedSurfaces(loadmodel);
+       Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles);
+       Mod_Alias_CalculateBoundingBox();
+       Mod_Alias_MorphMesh_CompileFrames();
+
+       surface = loadmodel->data_surfaces;
+       surface->texture = loadmodel->data_textures;
+       surface->num_firsttriangle = 0;
+       surface->num_triangles = loadmodel->surfmesh.num_triangles;
+       surface->num_firstvertex = 0;
+       surface->num_vertices = loadmodel->surfmesh.num_vertices;
+
+       loadmodel->surfmesh.isanimated = false;
+
+       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];
+#endif
+}
+
 qboolean Mod_CanSeeBox_Trace(int numsamples, float t, dp_model_t *model, vec3_t eye, vec3_t minsX, vec3_t maxsX)
 {
        // we already have done PVS culling at this point...