]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - model_brush.c
implemented and debugged BIH (Bounding Interval Hierarchy) code, more
[xonotic/darkplaces.git] / model_brush.c
index 052d365041115e73405f5ca908a1a0cefc9285db..ee82cde804626d0f89e6b4be8af67bf734b2e1d0 100644 (file)
@@ -46,6 +46,7 @@ cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "select
 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"};
+cvar_t mod_collision_bih = {0, "mod_collision_bih", "0", "enables use of generated Bounding Interval Hierarchy tree instead of compiled bsp tree in collision code"};
 
 static texture_t mod_q1bsp_texture_solid;
 static texture_t mod_q1bsp_texture_sky;
@@ -75,6 +76,7 @@ void Mod_BrushInit(void)
        Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower);
        Cvar_RegisterVariable(&mod_q3bsp_nolightmaps);
        Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes);
+       Cvar_RegisterVariable(&mod_collision_bih);
 
        memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid));
        strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name));
@@ -522,17 +524,17 @@ static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *in
                surface = info->model->data_surfaces + *mark;
                if (surface->texture->supercontents & SUPERCONTENTS_SOLID)
                {
-                       if(surface->num_bboxstride > 0)
+                       if(surface->deprecatedq3num_bboxstride > 0)
                        {
                                int i, cnt, tri;
-                               cnt = (surface->num_triangles + surface->num_bboxstride - 1) / surface->num_bboxstride;
+                               cnt = (surface->num_triangles + surface->deprecatedq3num_bboxstride - 1) / surface->deprecatedq3num_bboxstride;
                                for(i = 0; i < cnt; ++i)
                                {
-                                       if(BoxesOverlap(surface->data_bbox6f + i * 6, surface->data_bbox6f + i * 6 + 3, info->absmin, info->absmax))
+                                       if(BoxesOverlap(surface->deprecatedq3data_bbox6f + i * 6, surface->deprecatedq3data_bbox6f + i * 6 + 3, info->absmin, info->absmax))
                                        {
-                                               for(k = 0; k < surface->num_bboxstride; ++k)
+                                               for(k = 0; k < surface->deprecatedq3num_bboxstride; ++k)
                                                {
-                                                       tri = i * surface->num_bboxstride + k;
+                                                       tri = i * surface->deprecatedq3num_bboxstride + k;
                                                        if(tri >= surface->num_triangles)
                                                                break;
                                                        Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, tri);
@@ -1017,7 +1019,7 @@ static int Mod_Q1BSP_PointSuperContents(struct model_s *model, int frame, const
        return Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num);
 }
 
-void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, texture_t *boxtexture)
+void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture)
 {
 #if 1
        colbrushf_t cbox;
@@ -1123,7 +1125,7 @@ void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cm
 #endif
 }
 
-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)
+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, const texture_t *boxtexture)
 {
        memset(trace, 0, sizeof(trace_t));
        trace->fraction = 1;
@@ -1209,7 +1211,7 @@ loc0:
                        surface = model->data_surfaces + node->firstsurface;
                        for (i = 0;i < node->numsurfaces;i++, surface++)
                        {
-                               if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo->samples)
+                               if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo || !surface->lightmapinfo->samples)
                                        continue;       // no lightmaps
 
                                // location we want to sample in the lightmap
@@ -1567,7 +1569,7 @@ 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 == mtheight * 2)
                        {
-                               data = loadimagepixelsbgra(tx->name, false, false);
+                               data = loadimagepixelsbgra(tx->name, false, false, r_texture_convertsRGB_skin.integer);
                                if (data && image_width == image_height * 2)
                                {
                                        R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
@@ -3402,7 +3404,6 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        int i, j, k;
        dheader_t *header;
        dmodel_t *bm;
-       mempool_t *mainmempool;
        float dist, modelyawradius, modelradius;
        msurface_t *surface;
        int numshadowmeshtriangles;
@@ -3532,8 +3533,6 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
        mod->numframes = 2;             // regular and alternate animation
        mod->numskins = 1;
 
-       mainmempool = mod->mempool;
-
        // 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++)
@@ -4481,8 +4480,16 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
        if (l->filelen % sizeof(int[3]))
                Host_Error("Mod_Q3BSP_LoadTriangles: funny lump size in %s",loadmodel->name);
        count = l->filelen / sizeof(*in);
-       out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
 
+       if(!loadmodel->brushq3.num_vertices)
+       {
+               if (count)
+                       Con_Printf("Mod_Q3BSP_LoadTriangles: %s has triangles but no vertexes, broken compiler, ignoring problem\n", loadmodel->name);
+               loadmodel->brushq3.num_triangles = 0;
+               return;
+       }
+
+       out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out));
        loadmodel->brushq3.num_triangles = count / 3;
        loadmodel->brushq3.data_element3i = out;
 
@@ -4492,8 +4499,6 @@ 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;
                }
        }
@@ -4502,7 +4507,7 @@ static void Mod_Q3BSP_LoadTriangles(lump_t *l)
 static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
 {
        q3dlightmap_t *input_pointer;
-       int i, j, k, count, power, power2, mask, endlightmap, mergewidth, mergeheight;
+       int i, j, k, count, power, power2, endlightmap, mergewidth, mergeheight;
        unsigned char *c;
 
        unsigned char *convertedpixels;
@@ -4546,7 +4551,7 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
                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);
+               inpixels[0] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, 0), false, false, false);
                if(!inpixels[0])
                        return;
 
@@ -4566,7 +4571,7 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
 
                for(count = 1; ; ++count)
                {
-                       inpixels[count] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, count), false, false);
+                       inpixels[count] = loadimagepixelsbgra(va("%s/lm_%04d", mapname, count), false, false, false);
                        if(!inpixels[count])
                                break; // we got all of them
                        if(image_width != size || image_height != size)
@@ -4670,7 +4675,6 @@ static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump)
 
        power = loadmodel->brushq3.num_lightmapmergepower;
        power2 = power * 2;
-       mask = (1 << power) - 1;
        for (i = 0;i < count;i++)
        {
                // figure out which merged lightmap texture this fits into
@@ -4801,7 +4805,7 @@ 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, cxtess, cytess;
+       int i, oldi, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xtess, ytess, finalvertices, finaltriangles, firstvertex, firstelement, type, oldnumtriangles, oldnumtriangles2, meshvertices, meshtriangles, collisionvertices, collisiontriangles, numvertices, numtriangles, cxtess, cytess;
        float lightmaptcbase[2], lightmaptcscale[2];
        //int *originalelement3i;
        //int *originalneighbor3i;
@@ -4812,6 +4816,8 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
        float *originalcolor4f;
        float *originaltexcoordtexture2f;
        float *originaltexcoordlightmap2f;
+       float *surfacecollisionvertex3f;
+       int *surfacecollisionelement3i;
        float *v;
        patchtess_t *patchtess = NULL;
        int patchtesscount = 0;
@@ -4993,6 +4999,8 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
        while (again);
 
        // Calculate resulting number of triangles
+       collisionvertices = 0;
+       collisiontriangles = 0;
        for(i = 0; i < patchtesscount; ++i)
        {
                finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_VISUAL].xtess);
@@ -5004,14 +5012,31 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                oldout[patchtess[i].surface_id].num_triangles = numtriangles;
                meshvertices += oldout[patchtess[i].surface_id].num_vertices;
                meshtriangles += oldout[patchtess[i].surface_id].num_triangles;
+
+               finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_COLLISION].xtess);
+               finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_COLLISION].ytess);
+               numvertices = finalwidth * finalheight;
+               numtriangles = (finalwidth - 1) * (finalheight - 1) * 2;
+
+               oldout[patchtess[i].surface_id].num_collisionvertices = numvertices;
+               oldout[patchtess[i].surface_id].num_collisiontriangles = numtriangles;
+               collisionvertices += oldout[patchtess[i].surface_id].num_collisionvertices;
+               collisiontriangles += oldout[patchtess[i].surface_id].num_collisiontriangles;
        }
 
        i = oldi;
        in = oldin;
        out = oldout;
        Mod_AllocSurfMesh(loadmodel->mempool, meshvertices, meshtriangles, false, true, false);
+       if (collisiontriangles)
+       {
+               loadmodel->brush.data_collisionvertex3f = Mem_Alloc(loadmodel->mempool, collisionvertices * sizeof(float[3]));
+               loadmodel->brush.data_collisionelement3i = Mem_Alloc(loadmodel->mempool, collisiontriangles * sizeof(int[3]));
+       }
        meshvertices = 0;
        meshtriangles = 0;
+       collisionvertices = 0;
+       collisiontriangles = 0;
        for (;i < count && meshvertices + out->num_vertices <= loadmodel->surfmesh.num_vertices;i++, in++, out++)
        {
                if (out->num_vertices < 3 || out->num_triangles < 1)
@@ -5022,6 +5047,7 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                firstelement = LittleLong(in->firstelement);
                out->num_firstvertex = meshvertices;
                out->num_firsttriangle = meshtriangles;
+               out->num_firstcollisiontriangle = collisiontriangles;
                switch(type)
                {
                case Q3FACETYPE_FLAT:
@@ -5102,26 +5128,40 @@ static void Mod_Q3BSP_LoadFaces(lump_t *l)
                        finalvertices = finalwidth * finalheight;
                        finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2;
 
-                       out->data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices);
-                       out->data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles);
+                       // legacy collision geometry implementation
+                       out->deprecatedq3data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices);
+                       out->deprecatedq3data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles);
                        out->num_collisionvertices = finalvertices;
                        out->num_collisiontriangles = finaltriangles;
-                       Q3PatchTesselateFloat(3, sizeof(float[3]), out->data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess);
-                       Q3PatchTriangleElements(out->data_collisionelement3i, finalwidth, finalheight, 0);
+                       Q3PatchTesselateFloat(3, sizeof(float[3]), out->deprecatedq3data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess);
+                       Q3PatchTriangleElements(out->deprecatedq3data_collisionelement3i, finalwidth, finalheight, 0);
 
                        //Mod_SnapVertices(3, out->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), 0.25);
-                       Mod_SnapVertices(3, out->num_collisionvertices, out->data_collisionvertex3f, 1);
+                       Mod_SnapVertices(3, out->num_collisionvertices, out->deprecatedq3data_collisionvertex3f, 1);
 
                        oldnumtriangles = out->num_triangles;
                        oldnumtriangles2 = out->num_collisiontriangles;
-                       out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(out->num_collisiontriangles, out->data_collisionelement3i, out->data_collisionelement3i, out->data_collisionvertex3f);
+                       out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(out->num_collisiontriangles, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_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);
+                       Mod_Q3BSP_BuildBBoxes(out->deprecatedq3data_collisionelement3i, out->num_collisiontriangles, out->deprecatedq3data_collisionvertex3f, &out->deprecatedq3data_collisionbbox6f, &out->deprecatedq3num_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->deprecatedq3data_bbox6f, &out->deprecatedq3num_bboxstride, mod_q3bsp_curves_stride.integer);
+
+                       // store collision geometry for BIH collision tree
+                       surfacecollisionvertex3f = loadmodel->brush.data_collisionvertex3f + collisionvertices * 3;
+                       surfacecollisionelement3i = loadmodel->brush.data_collisionelement3i + collisiontriangles * 3;
+                       Q3PatchTesselateFloat(3, sizeof(float[3]), surfacecollisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess);
+                       Q3PatchTriangleElements(surfacecollisionelement3i, finalwidth, finalheight, collisionvertices);
+                       Mod_SnapVertices(3, finalvertices, surfacecollisionvertex3f, 1);
+                       oldnumtriangles = out->num_triangles;
+                       oldnumtriangles2 = out->num_collisiontriangles;
+                       out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(out->num_collisiontriangles, surfacecollisionelement3i, surfacecollisionelement3i, loadmodel->brush.data_collisionvertex3f);
 
                        if (developer_extra.integer)
                                Con_DPrintf("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);
+
+                       collisionvertices += finalvertices;
+                       collisiontriangles += out->num_collisiontriangles;
                        break;
                default:
                        break;
@@ -5631,6 +5671,365 @@ static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t s
        }
 }
 
+static void Mod_BIH_TracePoint_RecursiveBIHNode(trace_t *trace, dp_model_t *model, int nodenum, const vec3_t point)
+{
+       const bih_leaf_t *leaf;
+       const bih_node_t *node;
+       const colbrushf_t *brush;
+       int axis;
+       while (nodenum >= 0)
+       {
+               node = model->collision_bih.nodes + nodenum;
+               axis = node->type - BIH_SPLITX;
+               if (point[axis] <= node->backmax)
+               {
+                       if (point[axis] >= node->frontmin)
+                               Mod_BIH_TracePoint_RecursiveBIHNode(trace, model, node->front, point);
+                       nodenum = node->back;
+               }
+               else if (point[axis] >= node->frontmin)
+                       nodenum = node->front;
+               else // no overlap with either child?  just return
+                       return;
+       }
+       if (!model->collision_bih.leafs)
+               return;
+       leaf = model->collision_bih.leafs + (-1-nodenum);
+       switch(leaf->type)
+       {
+       case BIH_LEAF:
+               // brush
+               brush = model->brush.data_brushes[leaf->itemindex].colbrushf;
+               Collision_TracePointBrushFloat(trace, point, brush);
+               break;
+       case BIH_LEAF + 1:
+               // triangle - skipped because they have no volume
+               break;
+       default:
+               break;
+       }
+}
+
+static void Mod_BIH_TraceLine_RecursiveBIHNode(trace_t *trace, dp_model_t *model, int nodenum, const vec3_t start, const vec3_t end, const vec3_t linestart, const vec3_t lineend)
+{
+       const bih_leaf_t *leaf;
+       const bih_node_t *node;
+       const colbrushf_t *brush;
+       const int *e;
+       const texture_t *texture;
+       int axis;
+#if 0
+       int sideflags;
+       vec_t frontdist1;
+       vec_t frontdist2;
+       vec_t frontfrac;
+       vec_t backdist1;
+       vec_t backdist2;
+       vec_t backfrac;
+       vec3_t clipped[2];
+#endif
+       vec3_t segmentmins;
+       vec3_t segmentmaxs;
+       segmentmins[0] = min(start[0], end[0]);
+       segmentmins[1] = min(start[1], end[1]);
+       segmentmins[2] = min(start[2], end[2]);
+       segmentmaxs[0] = max(start[0], end[0]);
+       segmentmaxs[1] = max(start[1], end[1]);
+       segmentmaxs[2] = max(start[2], end[2]);
+       while (nodenum >= 0)
+       {
+               node = model->collision_bih.nodes + nodenum;
+#if 0
+               if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs))
+                       return;
+#endif
+               axis = node->type - BIH_SPLITX;
+#if 1
+               if (segmentmins[axis] <= node->backmax)
+               {
+                       if (segmentmaxs[axis] >= node->frontmin)
+                               Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, end, linestart, lineend);
+                       nodenum = node->back;
+               }
+               else if (segmentmaxs[axis] >= node->frontmin)
+                       nodenum = node->front;
+               else
+                       return; // trace falls between children
+#else
+               frontdist1 = start[axis] - node->backmax;
+               frontdist2 = end[axis] - node->backmax;
+               backdist1 = start[axis] - node->frontmin;
+               backdist2 = end[axis] - node->frontmin;
+               sideflags = 0;
+               if (frontdist1 < 0)
+                       sideflags |= 1;
+               if (frontdist2 < 0)
+                       sideflags |= 2;
+               if (backdist1 < 0)
+                       sideflags |= 4;
+               if (backdist2 < 0)
+                       sideflags |= 8;
+               switch(sideflags)
+               {
+               case 0:
+                       // start end START END
+                       nodenum = node->front;
+                       continue;
+               case 1:
+                       // START end START END
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[0]);
+                       start = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->front;
+                       break;
+               case 2:
+                       // start END START END
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[0]);
+                       end = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->front;
+                       break;
+               case 3:
+                       // START END START END
+                       return; // line falls in gap between children
+               case 4:
+                       // start end start END
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, end, linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       end = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 5:
+                       // START end start END
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, clipped[1], end, linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       end = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 6:
+                       // start END start END
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, clipped[1], linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       end = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 7:
+                       // START END start END
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       end = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 8:
+                       // start end START end
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, end, linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       start = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 9:
+                       // START end START end
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, clipped[1], end, linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       start = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 10:
+                       // start END START end
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, clipped[1], linestart, lineend);
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       start = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 11:
+                       // START END START end
+                       backfrac = backdist1 / (backdist1 - backdist2);
+                       VectorLerp(start, backfrac, end, clipped[0]);
+                       start = clipped[0];
+                       segmentmins[0] = min(start[0], end[0]);
+                       segmentmins[1] = min(start[1], end[1]);
+                       segmentmins[2] = min(start[2], end[2]);
+                       segmentmaxs[0] = max(start[0], end[0]);
+                       segmentmaxs[1] = max(start[1], end[1]);
+                       segmentmaxs[2] = max(start[2], end[2]);
+                       nodenum = node->back;
+                       break;
+               case 12:
+                       // start end start end
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, end, linestart, lineend);
+                       nodenum = node->back;
+                       break;
+               case 13:
+                       // START end start end
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, clipped[1], end, linestart, lineend);
+                       nodenum = node->back;
+                       break;
+               case 14:
+                       // start END start end
+                       frontfrac = frontdist1 / (frontdist1 - frontdist2);
+                       VectorLerp(start, frontfrac, end, clipped[1]);
+                       Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, node->front, start, clipped[1], linestart, lineend);
+                       nodenum = node->back;
+                       break;
+               case 15:
+                       // START END start end
+                       nodenum = node->back;
+                       continue;
+               }
+#endif
+       }
+       if (!model->collision_bih.leafs)
+               return;
+       leaf = model->collision_bih.leafs + (-1-nodenum);
+#if 1
+       if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs))
+               return;
+#endif
+       switch(leaf->type)
+       {
+       case BIH_LEAF:
+               // brush
+               brush = model->brush.data_brushes[leaf->itemindex].colbrushf;
+               Collision_TraceLineBrushFloat(trace, linestart, lineend, brush, brush);
+               break;
+       case BIH_LEAF + 1:
+               // triangle
+               e = model->brush.data_collisionelement3i + 3*leaf->itemindex;
+               texture = model->data_textures + leaf->textureindex;
+               Collision_TraceLineTriangleFloat(trace, linestart, lineend, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture);
+               break;
+       default:
+               break;
+       }
+}
+
+static void Mod_BIH_TraceBrush_RecursiveBIHNode(trace_t *trace, dp_model_t *model, int nodenum, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const vec3_t segmentmins, const vec3_t segmentmaxs)
+{
+       const bih_leaf_t *leaf;
+       const bih_node_t *node;
+       const colbrushf_t *brush;
+       const int *e;
+       const texture_t *texture;
+       int axis;
+       while (nodenum >= 0)
+       {
+               node = model->collision_bih.nodes + nodenum;
+               axis = node->type - BIH_SPLITX;
+#if 0
+#if 0
+               if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs))
+                       return;
+#endif
+               Mod_BIH_TraceBrush_RecursiveBIHNode(trace, model, node->front, thisbrush_start, thisbrush_end, segmentmins, segmentmaxs);
+               nodenum = node->back;
+               continue;
+#endif
+               if (segmentmins[axis] <= node->backmax)
+               {
+                       if (segmentmaxs[axis] >= node->frontmin)
+                               Mod_BIH_TraceBrush_RecursiveBIHNode(trace, model, node->front, thisbrush_start, thisbrush_end, segmentmins, segmentmaxs);
+                       nodenum = node->back;
+               }
+               else if (segmentmaxs[axis] >= node->frontmin)
+                       nodenum = node->front;
+               else
+                       return; // trace falls between children
+       }
+       if (!model->collision_bih.leafs)
+               return;
+       leaf = model->collision_bih.leafs + (-1-nodenum);
+#if 1
+       if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs))
+               return;
+#endif
+       switch(leaf->type)
+       {
+       case BIH_LEAF:
+               // brush
+               brush = model->brush.data_brushes[leaf->itemindex].colbrushf;
+               Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush);
+               break;
+       case BIH_LEAF + 1:
+               // triangle
+               e = model->brush.data_collisionelement3i + 3*leaf->itemindex;
+               texture = model->data_textures + leaf->textureindex;
+               Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture);
+               break;
+       default:
+               break;
+       }
+}
+
 static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe)
 {
        int i;
@@ -5731,10 +6130,10 @@ static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *mod
                for (i = 0;i < leaf->numleafsurfaces;i++)
                {
                        surface = model->data_surfaces + leaf->firstleafsurface[i];
-                       if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
+                       if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != 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->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
+                               surface->deprecatedq3collisionmarkframe = markframe;
+                               Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
                        }
                }
        }
@@ -5812,10 +6211,10 @@ static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *mo
                for (i = 0;i < leaf->numleafsurfaces;i++)
                {
                        surface = model->data_surfaces + leaf->firstleafsurface[i];
-                       if (surface->num_collisiontriangles && surface->collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs))
+                       if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != 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->num_collisionbboxstride, surface->data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
+                               surface->deprecatedq3collisionmarkframe = markframe;
+                               Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs);
                        }
                }
        }
@@ -5831,7 +6230,9 @@ static void Mod_Q3BSP_TracePoint(dp_model_t *model, const frameblend_t *frameble
        trace->fraction = 1;
        trace->realfraction = 1;
        trace->hitsupercontentsmask = hitsupercontentsmask;
-       if (model->brush.submodel)
+       if (mod_collision_bih.integer)
+               Mod_BIH_TracePoint_RecursiveBIHNode(trace, model, model->collision_bih.rootnode, start);
+       else if (model->brush.submodel)
        {
                for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
                        if (brush->colbrushf)
@@ -5864,7 +6265,9 @@ static void Mod_Q3BSP_TraceLine(dp_model_t *model, const frameblend_t *frameblen
        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)
+       if (mod_collision_bih.integer)
+               Mod_BIH_TraceLine_RecursiveBIHNode(trace, model, model->collision_bih.rootnode, start, end, start, end);
+       else if (model->brush.submodel)
        {
                for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
                        if (brush->colbrushf)
@@ -5872,7 +6275,7 @@ static void Mod_Q3BSP_TraceLine(dp_model_t *model, const frameblend_t *frameblen
                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);
+                                       Collision_TraceLineTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_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);
@@ -5919,7 +6322,9 @@ static void Mod_Q3BSP_TraceBox(dp_model_t *model, const frameblend_t *frameblend
        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)
+       if (mod_collision_bih.integer)
+               Mod_BIH_TraceBrush_RecursiveBIHNode(trace, model, model->collision_bih.rootnode, &thisbrush_start.brush, &thisbrush_end.brush, segmentmins, segmentmaxs);
+       else if (model->brush.submodel)
        {
                for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
                        if (brush->colbrushf)
@@ -5927,7 +6332,7 @@ static void Mod_Q3BSP_TraceBox(dp_model_t *model, const frameblend_t *frameblend
                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);
+                                       Collision_TraceBrushTriangleMeshFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_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);
@@ -5938,8 +6343,14 @@ static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const
        int i;
        int supercontents = 0;
        q3mbrush_t *brush;
+       if (mod_collision_bih.integer)
+       {
+               trace_t trace;
+               Mod_Q3BSP_TracePoint(model, NULL, NULL, &trace, point, 0);
+               supercontents = trace.startsupercontents;
+       }
        // test if the point is inside each brush
-       if (model->brush.submodel)
+       else if (model->brush.submodel)
        {
                // submodels are effectively one leaf
                for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
@@ -5965,6 +6376,88 @@ static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const
        return supercontents;
 }
 
+void Mod_MakeCollisionData(dp_model_t *model)
+{
+       int j;
+       int bihnumleafs;
+       int bihmaxnodes;
+       int brushindex;
+       int triangleindex;
+       int bihleafindex;
+       int nummodelbrushes = model->nummodelbrushes;
+       int nummodelsurfaces = model->nummodelsurfaces;
+       const int *e;
+       const int *collisionelement3i;
+       const float *collisionvertex3f;
+       bih_leaf_t *bihleafs;
+       bih_node_t *bihnodes;
+       int *temp_leafsort;
+       int *temp_leafsortscratch;
+       const msurface_t *surface;
+       const q3mbrush_t *brush;
+
+       // find out how many BIH leaf nodes we need
+       bihnumleafs = model->nummodelbrushes;
+       surface = model->data_surfaces + model->firstmodelsurface;
+       for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++)
+               bihnumleafs += surface->num_collisiontriangles;
+       bihmaxnodes = bihnumleafs - 1;
+
+       // allocate the memory for the BIH leaf nodes
+       bihleafs = Mem_Alloc(loadmodel->mempool, sizeof(bih_leaf_t) * bihnumleafs);
+
+       // add BIH leaf nodes for all the collision brushes
+       bihleafindex = 0;
+       for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++)
+       {
+               bihleafs[bihleafindex].type = BIH_LEAF;
+               bihleafs[bihleafindex].textureindex = brush->texture - model->data_textures;
+               bihleafs[bihleafindex].itemindex = brushindex+model->firstmodelbrush;
+               VectorCopy(brush->colbrushf->mins, bihleafs[bihleafindex].mins);
+               VectorCopy(brush->colbrushf->maxs, bihleafs[bihleafindex].maxs);
+               bihleafindex++;
+       }
+
+       // add BIH leaf nodes for all the collision surfaces
+       collisionelement3i = model->brush.data_collisionelement3i;
+       collisionvertex3f = model->brush.data_collisionvertex3f;
+       for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++)
+       {
+               e = collisionelement3i + 3*surface->num_firstcollisiontriangle;
+               for (triangleindex = 0;triangleindex < surface->num_collisiontriangles;triangleindex++, e += 3)
+               {
+                       bihleafs[bihleafindex].type = BIH_LEAF + 1;
+                       bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures;
+                       bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firstcollisiontriangle;
+                       bihleafs[bihleafindex].mins[0] = min(collisionvertex3f[3*e[0]+0], min(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) - 1;
+                       bihleafs[bihleafindex].mins[1] = min(collisionvertex3f[3*e[0]+1], min(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) - 1;
+                       bihleafs[bihleafindex].mins[2] = min(collisionvertex3f[3*e[0]+2], min(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) - 1;
+                       bihleafs[bihleafindex].maxs[0] = max(collisionvertex3f[3*e[0]+0], max(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) + 1;
+                       bihleafs[bihleafindex].maxs[1] = max(collisionvertex3f[3*e[0]+1], max(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) + 1;
+                       bihleafs[bihleafindex].maxs[2] = max(collisionvertex3f[3*e[0]+2], max(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) + 1;
+                       bihleafindex++;
+               }
+       }
+
+       // allocate buffers for the produced and temporary data
+       bihnodes = Mem_Alloc(loadmodel->mempool, sizeof(bih_node_t) * bihmaxnodes);
+       temp_leafsort = Mem_Alloc(loadmodel->mempool, sizeof(int) * bihnumleafs * 2);
+       temp_leafsortscratch = temp_leafsort + bihnumleafs;
+
+       // now build it
+       BIH_Build(&model->collision_bih, bihnumleafs, bihleafs, bihmaxnodes, bihnodes, temp_leafsort, temp_leafsortscratch);
+
+       // we're done with the temporary data
+       Mem_Free(temp_leafsort);
+
+       // resize the BIH nodes array if it over-allocated
+       if (model->collision_bih.maxnodes > model->collision_bih.numnodes)
+       {
+               model->collision_bih.maxnodes = model->collision_bih.numnodes;
+               model->collision_bih.nodes = Mem_Realloc(loadmodel->mempool, model->collision_bih.nodes, model->collision_bih.numnodes * sizeof(bih_node_t));
+       }
+}
+
 static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents)
 {
        int supercontents = 0;
@@ -6174,7 +6667,8 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                        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);
+               if (loadmodel->brush.shadowmesh)
+                       Mod_BuildTriangleNeighbors(loadmodel->brush.shadowmesh->neighbor3i, loadmodel->brush.shadowmesh->element3i, loadmodel->brush.shadowmesh->numtriangles);
        }
 
        loadmodel->brush.num_leafs = 0;
@@ -6277,6 +6771,8 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
                if (j < mod->nummodelsurfaces)
                        mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
 
+               Mod_MakeCollisionData(mod);
+
                // generate VBOs and other shared data before cloning submodels
                if (i == 0)
                        Mod_BuildVBOs();
@@ -6676,7 +7172,7 @@ void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend)
        {
                VectorCopy(vertexhashdata[j].v, loadmodel->surfmesh.data_vertex3f + 3*j);
                VectorCopy(vertexhashdata[j].vn, loadmodel->surfmesh.data_normal3f + 3*j);
-               VectorCopy(vertexhashdata[j].vt, loadmodel->surfmesh.data_texcoordtexture2f + 2*j);
+               Vector2Copy(vertexhashdata[j].vt, loadmodel->surfmesh.data_texcoordtexture2f + 2*j);
        }
 
        // load the textures