From 8c4a05a0e34eb4c21be65315d58841c0f8268197 Mon Sep 17 00:00:00 2001
From: havoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Mon, 15 Feb 2010 12:56:48 +0000
Subject: [PATCH] implementing Bounding Interval Hierarchy collision culling
 trees for q3bsp, not used yet

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@9957 d7cf8633-e32d-0410-b094-e92efae38249
---
 bih.c          | 140 +++++++++++++++++++++++++++++++++++++++++
 bih.h          |  68 ++++++++++++++++++++
 gl_rmain.c     |  49 ++++++++++-----
 makefile.inc   |   1 +
 model_brush.c  | 164 ++++++++++++++++++++++++++++++++++++++++++-------
 model_shared.h |  24 +++++---
 6 files changed, 401 insertions(+), 45 deletions(-)
 create mode 100644 bih.c
 create mode 100644 bih.h

diff --git a/bih.c b/bih.c
new file mode 100644
index 00000000..caf6e7e1
--- /dev/null
+++ b/bih.c
@@ -0,0 +1,140 @@
+
+// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain.
+
+#include <stdlib.h>
+#include <memory.h>
+#include "bih.h"
+
+static int BIH_BuildNode(bih_t *bih, int numchildren, int *leaflist)
+{
+	int i;
+	int j;
+	int longestaxis;
+	int axis;
+	int nodenum;
+	int front;
+	int back;
+	bih_node_t *node;
+	bih_leaf_t *child;
+	float splitdist;
+	float d;
+	float mins[3];
+	float maxs[3];
+	float size[3];
+	if (numchildren < 2)
+		return -1-leaflist[0];
+	// if we run out of nodes it's the caller's fault, but don't crash
+	if (bih->numnodes == bih->maxnodes)
+	{
+		if (!bih->error)
+			bih->error = BIHERROR_OUT_OF_NODES;
+		return -1-leaflist[0];
+	}
+	nodenum = bih->numnodes++;
+	node = bih->nodes + nodenum;
+	// calculate bounds of children
+	child = bih->leafs + leaflist[0];
+	mins[0] = child->mins[0];
+	mins[1] = child->mins[1];
+	mins[2] = child->mins[2];
+	maxs[0] = child->maxs[0];
+	maxs[1] = child->maxs[1];
+	maxs[2] = child->maxs[2];
+	for (i = 1;i < numchildren;i++)
+	{
+		child = bih->leafs + leaflist[i];
+		if (mins[0] > child->mins[0]) mins[0] = child->mins[0];
+		if (mins[1] > child->mins[1]) mins[1] = child->mins[1];
+		if (mins[2] > child->mins[2]) mins[2] = child->mins[2];
+		if (maxs[0] < child->maxs[0]) maxs[0] = child->maxs[0];
+		if (maxs[1] < child->maxs[1]) maxs[1] = child->maxs[1];
+		if (maxs[2] < child->maxs[2]) maxs[2] = child->maxs[2];
+	}
+	size[0] = maxs[0] - mins[0];
+	size[1] = maxs[1] - mins[1];
+	size[2] = maxs[2] - mins[2];
+	// store bounds for node
+	node->mins[0] = mins[0];
+	node->mins[1] = mins[1];
+	node->mins[2] = mins[2];
+	node->maxs[0] = maxs[0];
+	node->maxs[1] = maxs[1];
+	node->maxs[2] = maxs[2];
+	// pick longest axis
+	longestaxis = 0;
+	if (size[0] < size[1]) longestaxis = 1;
+	if (size[longestaxis] < size[2]) longestaxis = 2;
+	// iterate possible split axis choices:
+	// longest (best raytracing performance)
+	// x (longest had identical children, try X axis)
+	// y (longest and X axis had identical children, try Y axis)
+	// z (longest and X and Y axes had identical children, try Z axis)
+	// arbitrary (all children have same bounds, divide the list)
+	for (j = 0;j < 3;j++)
+	{
+		// if longest axis fails, we try each one until something works
+		// (this also can fail, see below)
+		switch(j)
+		{
+		default:
+		case 0: axis = longestaxis;break;
+		case 1: axis = (longestaxis + 1) % 3;break;
+		case 2: axis = (longestaxis + 2) % 3;break;
+		}
+		// sort children into front and back lists
+		node->type = BIH_SPLITX + axis;
+		splitdist = (node->mins[axis] + node->maxs[axis]) * 0.5f;
+		front = 0;
+		back = 0;
+		for (i = 0;i < numchildren;i++)
+		{
+			child = bih->leafs + leaflist[i];
+			d = (child->mins[axis] + child->maxs[axis]) * 0.5f;
+			if (d < splitdist)
+				bih->leafsortscratch[back++] = leaflist[i];
+			else
+				leaflist[front++] = leaflist[i];
+		}
+		// now copy the back ones into the space made in the leaflist for them
+		if (back)
+			memcpy(leaflist + front, bih->leafsortscratch, back*sizeof(leaflist[0]));
+		// if both sides have some children, it's good enough for us.
+		if (front && back)
+			break;
+	}
+	if (j == 3)
+	{
+		// the almost impossible case happened; all children have identical
+		// bounds, so just divide them arbitrarily into two lists.
+		node->type = BIH_SPLITX;
+		back = numchildren >> 1;
+		front = numchildren - back;
+	}
+
+	// we now have front and back children divided in leaflist...
+	node->children[0] = BIH_BuildNode(bih, front, leaflist);
+	node->children[1] = BIH_BuildNode(bih, back, leaflist + front);
+	return nodenum;
+}
+
+int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch)
+{
+	int i;
+
+	memset(bih, 0, sizeof(*bih));
+	bih->numleafs = numleafs;
+	bih->leafs = leafs;
+	bih->leafsort = temp_leafsort;
+	bih->leafsortscratch = temp_leafsortscratch;
+	bih->numnodes = 0;
+	bih->maxnodes = maxnodes;
+	bih->nodes = nodes;
+
+	// clear things we intend to rebuild
+	memset(bih->nodes, 0, sizeof(bih->nodes[0]) * bih->maxnodes);
+	for (i = 0;i < bih->numleafs;i++)
+		bih->leafsort[i] = i;
+
+	BIH_BuildNode(bih, bih->numleafs, bih->leafsort);
+	return bih->error;
+}
diff --git a/bih.h b/bih.h
new file mode 100644
index 00000000..7a98045f
--- /dev/null
+++ b/bih.h
@@ -0,0 +1,68 @@
+
+// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain.
+
+// Based on information in http://zach.in.tu-clausthal.de/papers/vrst02.html (in particular vrst02_boxtree.pdf)
+
+#ifndef BIH_H
+#define BIH_H
+
+typedef enum biherror_e
+{
+	BIHERROR_OK, // no error, be happy
+	BIHERROR_OUT_OF_NODES, // could not produce complete hierarchy, maxnodes too low (should be roughly half of numleafs)
+}
+biherror_t;
+
+typedef enum bih_nodetype_e
+{
+	BIH_SPLITX = 0,
+	BIH_SPLITY = 1,
+	BIH_SPLITZ = 2,
+	BIH_LEAF = 3,
+}
+bih_nodetype_t;
+
+typedef struct bih_node_s
+{
+	bih_nodetype_t type; // = BIH_SPLITX and similar values
+	// TODO: store just one float for distance, and have BIH_SPLITMINX and BIH_SPLITMAXX distinctions, to reduce memory footprint and traversal time, as described in the paper (vrst02_boxtree.pdf)
+	// TODO: move bounds data to parent node and remove it from leafs?
+	float mins[3];
+	float maxs[3];
+	// < 0 is a leaf index (-1-leafindex), >= 0 is another node index (always >= this node's index)
+	int children[2];
+}
+bih_node_t;
+
+typedef struct bih_leaf_s
+{
+	bih_nodetype_t type; // = BIH_LEAF
+	float mins[3];
+	float maxs[3];
+	// data past this point is generic and entirely up to the caller...
+	int textureindex;
+	int itemindex; // triangle or brush index
+}
+bih_leaf_t;
+
+typedef struct bih_s
+{
+	// permanent fields
+	// leafs are constructed by caller before calling BIH_Build
+	int numleafs;
+	bih_leaf_t *leafs;
+	// nodes are constructed by BIH_Build
+	int numnodes;
+	bih_node_t *nodes;
+
+	// fields used only during BIH_Build:
+	int maxnodes;
+	int error; // set to a value if an error occurs in building (such as numnodes == maxnodes)
+	int *leafsort;
+	int *leafsortscratch;
+}
+bih_t;
+
+int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch);
+
+#endif
diff --git a/gl_rmain.c b/gl_rmain.c
index c2f1a1cf..ad459952 100644
--- a/gl_rmain.c
+++ b/gl_rmain.c
@@ -12113,7 +12113,6 @@ void R_DrawDebugModel(void)
 {
 	entity_render_t *ent = rsurface.entity;
 	int i, j, k, l, flagsmask;
-	q3mbrush_t *brush;
 	const msurface_t *surface;
 	dp_model_t *model = ent->model;
 	vec3_t v;
@@ -12128,25 +12127,43 @@ void R_DrawDebugModel(void)
 	GL_DepthMask(false);
 	GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
-	if (r_showcollisionbrushes.value > 0 && model->brush.num_brushes)
+	if (r_showcollisionbrushes.value > 0 && model->collision_bih.numleafs)
 	{
+		int triangleindex;
+		int bihleafindex;
+		qboolean cullbox = ent == r_refdef.scene.worldentity;
+		const q3mbrush_t *brush;
+		const bih_t *bih = &model->collision_bih;
+		const bih_leaf_t *bihleaf;
+		float vertex3f[3][3];
 		GL_PolygonOffset(r_refdef.polygonfactor + r_showcollisionbrushes_polygonfactor.value, r_refdef.polygonoffset + r_showcollisionbrushes_polygonoffset.value);
-		for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++)
+		for (bihleafindex = 0, bihleaf = bih->leafs;bihleafindex < bih->numleafs;bihleafindex++, bihleaf++)
 		{
-			if (brush->colbrushf && brush->colbrushf->numtriangles)
-			{
-				R_Mesh_VertexPointer(brush->colbrushf->points->v, 0, 0);
-				GL_Color((i & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((i >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((i >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value);
-				R_Mesh_Draw(0, brush->colbrushf->numpoints, 0, brush->colbrushf->numtriangles, brush->colbrushf->elements, NULL, 0, 0);
-			}
-		}
-		for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++)
-		{
-			if (surface->num_collisiontriangles)
+			if (cullbox && R_CullBox(bihleaf->mins, bihleaf->maxs))
+				continue;
+			switch (bihleaf->type)
 			{
-				R_Mesh_VertexPointer(surface->data_collisionvertex3f, 0, 0);
-				GL_Color((i & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((i >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((i >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value);
-				R_Mesh_Draw(0, surface->num_collisionvertices, 0, surface->num_collisiontriangles, surface->data_collisionelement3i, NULL, 0, 0);
+			case BIH_LEAF:
+				// brush
+				brush = model->brush.data_brushes + bihleaf->itemindex;
+				if (brush->colbrushf && brush->colbrushf->numtriangles)
+				{
+					GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value);
+					R_Mesh_Draw(0, brush->colbrushf->numpoints, 0, brush->colbrushf->numtriangles, brush->colbrushf->elements, NULL, 0, 0);
+				}
+				break;
+			case BIH_LEAF + 1:
+				// triangle
+				triangleindex = bihleaf->itemindex;
+				VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+0], vertex3f[0]);
+				VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+1], vertex3f[1]);
+				VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+2], vertex3f[2]);
+				R_Mesh_VertexPointer(vertex3f[0], 0, 0);
+				GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value);
+				R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, polygonelement3s, 0, 0);
+				break;
+			default:
+				break;
 			}
 		}
 	}
diff --git a/makefile.inc b/makefile.inc
index 22478d5d..1c341202 100644
--- a/makefile.inc
+++ b/makefile.inc
@@ -88,6 +88,7 @@ OBJ_NOCD=cd_null.o
 
 # Common objects
 OBJ_COMMON= \
+	bih.o \
 	cap_avi.o \
 	cap_ogg.o \
 	cd_shared.o \
diff --git a/model_brush.c b/model_brush.c
index 8aa9c063..dff011ed 100644
--- a/model_brush.c
+++ b/model_brush.c
@@ -522,17 +522,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);
@@ -4797,7 +4797,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;
@@ -4808,6 +4808,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;
@@ -4989,6 +4991,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);
@@ -5000,14 +5004,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)
@@ -5018,6 +5039,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:
@@ -5098,26 +5120,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;
@@ -5727,10 +5763,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);
 			}
 		}
 	}
@@ -5808,10 +5844,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);
 			}
 		}
 	}
@@ -5868,7 +5904,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);
@@ -5923,7 +5959,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);
@@ -5961,6 +5997,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->mins, 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 + surface->num_firstcollisiontriangle;
+		for (triangleindex = 0;triangleindex < surface->num_collisiontriangles;triangleindex++)
+		{
+			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]));
+			bihleafs[bihleafindex].mins[1] = min(collisionvertex3f[3*e[0]+1], min(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1]));
+			bihleafs[bihleafindex].mins[2] = min(collisionvertex3f[3*e[0]+2], min(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2]));
+			bihleafs[bihleafindex].maxs[0] = max(collisionvertex3f[3*e[0]+0], max(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0]));
+			bihleafs[bihleafindex].maxs[1] = max(collisionvertex3f[3*e[0]+1], max(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1]));
+			bihleafs[bihleafindex].maxs[2] = max(collisionvertex3f[3*e[0]+2], max(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2]));
+			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;
@@ -6273,6 +6391,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();
diff --git a/model_shared.h b/model_shared.h
index 5ea12a22..96e9fb38 100644
--- a/model_shared.h
+++ b/model_shared.h
@@ -579,10 +579,11 @@ typedef struct msurface_s
 	// fog volume info in q3bsp
 	struct q3deffect_s *effect; // q3bsp
 	// mesh information for collisions (only used by q3bsp curves)
-	int *data_collisionelement3i; // q3bsp
-	float *data_collisionvertex3f; // q3bsp
-	float *data_collisionbbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles
-	float *data_bbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles
+	int num_firstcollisiontriangle;
+	int *deprecatedq3data_collisionelement3i; // q3bsp
+	float *deprecatedq3data_collisionvertex3f; // q3bsp
+	float *deprecatedq3data_collisionbbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles
+	float *deprecatedq3data_bbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles
 
 	// surfaces own ranges of vertices and triangles in the model->surfmesh
 	int num_triangles; // number of triangles
@@ -596,14 +597,15 @@ typedef struct msurface_s
 	// mesh information for collisions (only used by q3bsp curves)
 	int num_collisiontriangles; // q3bsp
 	int num_collisionvertices; // q3bsp
-	int num_collisionbboxstride;
-	int num_bboxstride;
+	int deprecatedq3num_collisionbboxstride;
+	int deprecatedq3num_bboxstride;
 	// FIXME: collisionmarkframe should be kept in a separate array
-	int collisionmarkframe; // q3bsp // don't collide twice in one trace
+	int deprecatedq3collisionmarkframe; // q3bsp // don't collide twice in one trace
 }
 msurface_t;
 
 #include "matrixlib.h"
+#include "bih.h"
 
 #include "model_brush.h"
 #include "model_sprite.h"
@@ -683,6 +685,12 @@ typedef struct model_brush_s
 	//pvschain = model->brush.data_pvsclusters + mycluster * model->brush.num_pvsclusterbytes;
 	//if (pvschain[thatcluster >> 3] & (1 << (thatcluster & 7)))
 
+	// collision geometry for q3 curves
+	int num_collisionvertices;
+	int num_collisiontriangles;
+	float *data_collisionvertex3f;
+	int *data_collisionelement3i;
+
 	// a mesh containing all shadow casting geometry for the whole model (including submodels), portions of this are referenced by each surface's num_firstshadowmeshtriangle
 	shadowmesh_t *shadowmesh;
 
@@ -868,6 +876,8 @@ typedef struct model_s
 	// range of collision brush numbers in this (sub)model
 	int				firstmodelbrush;
 	int				nummodelbrushes;
+	// BIH (Bounding Interval Hierarchy) for this (sub)model
+	bih_t			collision_bih;
 	// for md3 models
 	int				num_tags;
 	int				num_tagframes;
-- 
2.39.5